import { i18n } from '../i18n.js' const POW_SERVER = 'https://pow.kashilo.com' let modalScriptLoaded = false let modalScriptLoading = null /** * Load BTCPay modal checkout script once */ async function ensureModalLoaded() { if (modalScriptLoaded) return if (modalScriptLoading) return modalScriptLoading modalScriptLoading = new Promise((resolve, reject) => { const script = document.createElement('script') script.src = 'https://pay.xmr.rocks/modal/btcpay.js' script.onload = () => { modalScriptLoaded = true resolve() } script.onerror = () => reject(new Error('Failed to load BTCPay checkout')) document.head.appendChild(script) }) return modalScriptLoading } /** * Create a payment invoice for a listing * @param {string} listingId - The listing UUID * @returns {Promise} { invoiceId, checkoutLink, status, expirationTime } */ export async function createInvoice(listingId) { const response = await fetch(`${POW_SERVER}/btcpay/invoice`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ listingId }) }) if (!response.ok) { const error = await response.json().catch(() => ({})) throw new Error(error.error || 'Failed to create invoice') } return response.json() } /** * Check invoice payment status * @param {string} invoiceId * @returns {Promise} { invoiceId, status, additionalStatus } */ export async function getInvoiceStatus(invoiceId) { const response = await fetch(`${POW_SERVER}/btcpay/status?id=${encodeURIComponent(invoiceId)}`) if (!response.ok) { const error = await response.json().catch(() => ({})) throw new Error(error.error || 'Failed to check invoice status') } return response.json() } /** * Open the BTCPay modal checkout * @param {string} invoiceId * @returns {Promise} Final status when modal closes */ export async function openCheckout(invoiceId) { await ensureModalLoaded() return new Promise((resolve) => { let lastStatus = null window.btcpay.onModalReceiveMessage((msg) => { if (msg?.status) lastStatus = msg.status }) window.btcpay.onModalWillLeave(() => { resolve(lastStatus || 'unknown') }) const lang = i18n.getLocale() || 'en' window.btcpay.showInvoice(invoiceId, { lang }) }) } /** * Poll invoice status until settled, expired, or timeout * @param {string} invoiceId * @param {Object} [options] * @param {number} [options.interval=4000] - Poll interval in ms * @param {number} [options.timeout=900000] - Timeout in ms (default 15min) * @param {Function} [options.onUpdate] - Callback on status change * @returns {Promise} Final status object */ export async function pollUntilDone(invoiceId, options = {}) { const { interval = 4000, timeout = 900000, onUpdate = null } = options const startTime = Date.now() let lastStatus = null while (Date.now() - startTime < timeout) { const result = await getInvoiceStatus(invoiceId) if (result.status !== lastStatus) { lastStatus = result.status if (onUpdate) onUpdate(result) } if (['Settled', 'Expired', 'Invalid'].includes(result.status)) { return result } await new Promise(r => setTimeout(r, interval)) } throw new Error('Payment check timed out') } /** * Check if a listing has a pending invoice stored locally * @param {string} listingId * @returns {Object|null} { invoiceId, createdAt } */ export function getPendingInvoice(listingId) { try { const data = localStorage.getItem(`kashilo_invoice_${listingId}`) return data ? JSON.parse(data) : null } catch (e) { return null } } /** * Store a pending invoice reference locally * @param {string} listingId * @param {string} invoiceId */ export function savePendingInvoice(listingId, invoiceId) { try { localStorage.setItem(`kashilo_invoice_${listingId}`, JSON.stringify({ invoiceId, createdAt: Date.now() })) } catch (e) { // Storage unavailable } } /** * Remove pending invoice reference * @param {string} listingId */ export function clearPendingInvoice(listingId) { localStorage.removeItem(`kashilo_invoice_${listingId}`) } export default { createInvoice, getInvoiceStatus, openCheckout, pollUntilDone, getPendingInvoice, savePendingInvoice, clearPendingInvoice }