diff --git a/src/modules/restaurant/composables/useCheckout.ts b/src/modules/restaurant/composables/useCheckout.ts index c6ea72b..d922c29 100644 --- a/src/modules/restaurant/composables/useCheckout.ts +++ b/src/modules/restaurant/composables/useCheckout.ts @@ -24,7 +24,7 @@ import { ref, type Ref } from 'vue' import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { useAuth } from '@/composables/useAuthService' -import type WalletService from '@/modules/wallet/services/WalletService' +import appConfig from '@/app.config' import type { RestaurantAPI } from '../services/RestaurantAPI' import { useCartStore, type CartLine } from '../stores/cart' import type { @@ -90,10 +90,44 @@ function buildCreateOrder( export function useCheckout(): UseCheckoutReturn { const api = injectService(SERVICE_TOKENS.RESTAURANT_API) - const wallet = injectService(SERVICE_TOKENS.WALLET_SERVICE) const cart = useCartStore() const { user } = useAuth() + // We talk to LNbits's payments endpoint directly rather than + // pulling in the whole `wallet` module — the restaurant-app + // bundle is a customer surface, not a wallet UI, and `LnbitsAPI` + // is already registered by base. The customer's adminkey lives + // on AuthService.user.wallets[0].adminkey. + const apiBaseUrl = + ( + appConfig.modules.restaurant as + | { config?: { apiBaseUrl?: string } } + | undefined + )?.config?.apiBaseUrl || '' + + async function payBolt11(bolt11: string, adminkey: string): Promise { + const response = await fetch(`${apiBaseUrl}/api/v1/payments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': adminkey, + }, + body: JSON.stringify({ out: true, bolt11 }), + }) + if (!response.ok) { + let detail = response.statusText + try { + const body = await response.json() + if (body?.detail) detail = body.detail + } catch { + /* body wasn't JSON */ + } + throw new Error( + `Payment failed: ${response.status} ${detail}` + ) + } + } + const state = ref({ step: 'idle', progress: { current: 0, total: 0 }, @@ -141,27 +175,32 @@ export function useCheckout(): UseCheckoutReturn { quotes.push({ ...b, msat: quote.required_msat }) } - // 2. Pre-flight balance check. The webapp's WalletService - // exposes balance via PaymentService; we ask the service for - // the current cached value via its public computed. - // NOTE: balance values in this codebase are sats, not msat — - // convert before comparing. + // 2. Pre-flight balance check using AuthService's cached wallet + // balance (LNbits's user object carries balance_msat per wallet). const totalMsatRequired = quotes.reduce((s, q) => s + q.msat, 0) - const balanceSat = ( - wallet as unknown as { balance?: { value?: number } } - ).balance?.value - if (typeof balanceSat === 'number') { - const balanceMsat = balanceSat * 1000 - if (balanceMsat < totalMsatRequired) { + const wallet0 = user.value?.wallets?.[0] + if (wallet0 && typeof wallet0.balance_msat === 'number') { + if (wallet0.balance_msat < totalMsatRequired) { + const needSat = Math.ceil(totalMsatRequired / 1000) + const haveSat = Math.floor(wallet0.balance_msat / 1000) state.value = { step: 'error', progress: { current: 0, total: buckets.length }, currentRestaurantSlug: null, - error: `Insufficient balance. Need ${Math.ceil(totalMsatRequired / 1000)} sat, have ${balanceSat} sat.`, + error: `Insufficient balance. Need ${needSat} sat, have ${haveSat} sat.`, } throw new Error(state.value.error!) } } + if (!wallet0?.adminkey) { + state.value = { + step: 'error', + progress: { current: 0, total: buckets.length }, + currentRestaurantSlug: null, + error: 'No wallet available — please log in first.', + } + throw new Error(state.value.error!) + } // 3. Place orders. state.value.step = 'placing' @@ -196,13 +235,20 @@ export function useCheckout(): UseCheckoutReturn { state.value.progress = { current: i, total: placed.length } if (!p.invoice) continue // cash orders skip payment - const success = await wallet.sendPayment({ - amount: Math.ceil(p.invoice.amount_msat / 1000), - destination: p.invoice.bolt11, - comment: `Order at ${p.restaurantSlug}`, - }) - if (success && p.invoice.payment_hash) { - paidHashes.push(p.invoice.payment_hash) + try { + await payBolt11(p.invoice.bolt11, wallet0.adminkey) + if (p.invoice.payment_hash) { + paidHashes.push(p.invoice.payment_hash) + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + state.value = { + step: 'error', + progress: { current: i, total: placed.length }, + currentRestaurantSlug: p.restaurantSlug, + error: msg, + } + throw err } }