171 lines
4.5 KiB
JavaScript
171 lines
4.5 KiB
JavaScript
const POW_SERVER = 'https://pow.dgray.io'
|
|
|
|
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
|
|
* @param {string} [currency='EUR'] - Payment currency
|
|
* @returns {Promise<Object>} { invoiceId, checkoutLink, status, expirationTime }
|
|
*/
|
|
export async function createInvoice(listingId, currency = 'EUR') {
|
|
const response = await fetch(`${POW_SERVER}/btcpay/invoice`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ listingId, currency })
|
|
})
|
|
|
|
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<Object>} { 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<string>} 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')
|
|
})
|
|
|
|
window.btcpay.showInvoice(invoiceId)
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 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<Object>} 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(`dgray_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(`dgray_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(`dgray_invoice_${listingId}`)
|
|
}
|
|
|
|
export default {
|
|
createInvoice,
|
|
getInvoiceStatus,
|
|
openCheckout,
|
|
pollUntilDone,
|
|
getPendingInvoice,
|
|
savePendingInvoice,
|
|
clearPendingInvoice
|
|
}
|