feat: BTCPay webhook for auto-publish after confirmation, processing badge

This commit is contained in:
2026-02-06 15:03:56 +01:00
parent fcf22617d0
commit d9202f9ca2
8 changed files with 191 additions and 16 deletions

View File

@@ -678,20 +678,21 @@ class PageCreate extends HTMLElement {
return
}
// Poll for final status after modal close
const result = await pollUntilDone(invoiceId, {
interval: 4000,
timeout: 60000,
onUpdate: (update) => {
if (update.status === 'Processing' && submitBtn) {
submitBtn.textContent = t('payment.processing')
}
}
})
// Check status once after modal close
const currentStatus = await getInvoiceStatus(invoiceId)
if (result.status === 'Settled') {
if (currentStatus.status === 'Settled') {
await this.onPaymentSuccess(listingId)
} else {
return
}
// Processing = payment received, waiting for confirmation
if (currentStatus.status === 'Processing') {
await this.onPaymentReceived(listingId)
return
}
if (currentStatus.status === 'Expired' || currentStatus.status === 'Invalid') {
await directus.updateListing(listingId, { payment_status: 'expired' })
clearPendingInvoice(listingId)
this.showError(t('payment.expired'))
@@ -700,6 +701,15 @@ class PageCreate extends HTMLElement {
submitBtn.disabled = false
submitBtn.textContent = t('create.publish')
}
return
}
// Still "New" - user closed modal without paying
this.showError(t('payment.failed'))
this.submitting = false
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = t('create.publish')
}
} catch (error) {
console.error('Payment failed:', error)
@@ -728,6 +738,15 @@ class PageCreate extends HTMLElement {
router.navigate(`/listing/${listingId}`)
}
async onPaymentReceived(listingId) {
await directus.updateListing(listingId, {
payment_status: 'processing'
})
clearPendingInvoice(listingId)
router.navigate(`/listing/${listingId}`)
}
showError(message) {
let errorDiv = this.querySelector('.form-error')
if (!errorDiv) {

View File

@@ -265,7 +265,21 @@ class PageListing extends HTMLElement {
renderSidebar() {
// Owner view: show edit button instead of contact
if (this.isOwner) {
const paymentProcessing = this.listing?.payment_status === 'processing'
return /* html */`
${paymentProcessing ? `
<div class="sidebar-card payment-processing-card">
<div class="processing-badge">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<strong>${t('payment.awaitingConfirmation')}</strong>
</div>
<p class="processing-hint">${t('payment.awaitingHint')}</p>
</div>
` : ''}
<div class="sidebar-card">
<a href="#/edit/${this.listingId}" class="btn btn-primary btn-lg sidebar-btn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -986,6 +1000,26 @@ style.textContent = /* css */`
text-align: center;
}
/* Payment Processing Badge */
page-listing .payment-processing-card {
background: var(--color-bg-secondary);
border: 1px solid var(--color-warning, #e6a700);
}
page-listing .processing-badge {
display: flex;
align-items: center;
gap: var(--space-sm);
color: var(--color-warning, #e6a700);
}
page-listing .processing-hint {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
margin-top: var(--space-sm);
line-height: 1.5;
}
/* Loading & Empty States */
page-listing .loading {
display: flex;