@@ -1372,6 +1391,8 @@
+
+
@@ -3351,8 +3372,11 @@
Set Discount
document.getElementById('invoiceProducts').innerHTML = 'Loading products...';
document.getElementById('invoiceProductSection').style.display = 'block';
document.getElementById('invoiceBillingSection').style.display = 'none';
+ document.getElementById('invoiceDraftReview').style.display = 'none';
document.getElementById('invoiceResult').style.display = 'none';
document.getElementById('sendInvoiceBtn').style.display = 'none';
+ document.getElementById('prepareDraftBtn').style.display = 'none';
+ document.getElementById('backToEditBtn').style.display = 'none';
document.getElementById('invoiceForm').reset();
// Pre-fill from org data
@@ -3375,6 +3399,9 @@
Set Discount
const data = await response.json();
const invoiceableProducts = data.products.filter(p => p.is_invoiceable);
+ // Sort products by price (lowest first for better UX)
+ invoiceableProducts.sort((a, b) => (a.amount_cents || 0) - (b.amount_cents || 0));
+
renderInvoiceProducts(invoiceableProducts);
} catch (error) {
console.error('Error loading products:', error);
@@ -3383,6 +3410,53 @@
Set Discount
}
}
+ function getRecommendedInvoiceProduct(products) {
+ // Get the org's revenue tier
+ let orgRevenueTier = orgData?.revenue_tier;
+
+ // If no revenue_tier set, try to derive from enrichment data
+ if (!orgRevenueTier && orgData?.enrichment_revenue_range) {
+ const rangeMap = {
+ 'under $1m': 'under_1m',
+ '$1m-$5m': '1m_5m',
+ '$5m-$50m': '5m_50m',
+ '$50m-$250m': '50m_250m',
+ '$250m-$1b': '250m_1b',
+ 'over $1b': '1b_plus'
+ };
+ const lowerRange = orgData.enrichment_revenue_range.toLowerCase();
+ for (const [key, value] of Object.entries(rangeMap)) {
+ if (lowerRange.includes(key.replace('$', '').replace('-', ' to '))) {
+ orgRevenueTier = value;
+ break;
+ }
+ }
+ }
+
+ if (!orgRevenueTier) return null;
+
+ // Find products that match the org's revenue tier
+ const matchingProducts = products.filter(p => {
+ if (!p.revenue_tiers || p.revenue_tiers.length === 0) return true;
+ return p.revenue_tiers.includes(orgRevenueTier);
+ });
+
+ // Return highest priced matching product (same logic as pricing section)
+ if (matchingProducts.length > 0) {
+ return matchingProducts.reduce((a, b) => (b.amount_cents || 0) > (a.amount_cents || 0) ? b : a);
+ }
+ return null;
+ }
+
+ function calculateDiscountedPrice(amountCents) {
+ if (orgData?.discount_percent) {
+ return Math.round(amountCents * (1 - orgData.discount_percent / 100));
+ } else if (orgData?.discount_amount_cents) {
+ return Math.max(0, amountCents - orgData.discount_amount_cents);
+ }
+ return amountCents;
+ }
+
function renderInvoiceProducts(products) {
const container = document.getElementById('invoiceProducts');
@@ -3391,54 +3465,277 @@
Set Discount
return;
}
- container.innerHTML = products.map(product => {
- const price = new Intl.NumberFormat('en-US', {
+ const recommendedProduct = getRecommendedInvoiceProduct(products);
+ const hasDiscount = orgData?.discount_percent || orgData?.discount_amount_cents;
+
+ // Show discount info at top if applicable
+ let headerHtml = '';
+ if (hasDiscount) {
+ const discountText = orgData.discount_percent
+ ? `${orgData.discount_percent}% discount`
+ : `${formatPriceCurrency(orgData.discount_amount_cents)} discount`;
+ headerHtml = `
+
+
+ ${discountText} will be applied
+
+
+ `;
+ }
+
+ container.innerHTML = headerHtml + products.map(product => {
+ const listPrice = product.amount_cents || 0;
+ const netPrice = hasDiscount ? calculateDiscountedPrice(listPrice) : listPrice;
+
+ const listPriceFormatted = new Intl.NumberFormat('en-US', {
style: 'currency',
- currency: 'USD'
- }).format(product.amount_cents / 100);
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0
+ }).format(listPrice / 100);
+
+ const netPriceFormatted = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0
+ }).format(netPrice / 100);
const interval = product.billing_interval ? `/${product.billing_interval}` : '';
const displayName = escapeHtml(product.display_name || product.product_name);
const lookupKey = escapeHtml(product.lookup_key);
const description = product.description ? escapeHtml(product.description) : '';
+ const isRecommended = recommendedProduct && product.lookup_key === recommendedProduct.lookup_key;
+
+ // Build price display
+ let priceHtml;
+ if (hasDiscount && listPrice !== netPrice) {
+ priceHtml = `
+
${listPriceFormatted}
+
${netPriceFormatted}${interval}
+ `;
+ } else {
+ priceHtml = `
${listPriceFormatted}${interval}`;
+ }
+
+ // Build recommended badge
+ const recommendedBadge = isRecommended
+ ? `
Recommended`
+ : '';
+
+ const borderColor = isRecommended ? 'var(--color-brand)' : 'var(--color-border)';
+ const borderWidth = isRecommended ? '2px' : '1px';
return `
-
`;
}).join('');
+
+ // Add event listeners to avoid XSS from inline onclick
+ container.querySelectorAll('.product-select-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ selectInvoiceProduct(
+ btn.dataset.lookupKey,
+ btn.dataset.displayName,
+ parseInt(btn.dataset.amount, 10)
+ );
+ });
+ btn.addEventListener('mouseover', () => {
+ btn.style.borderColor = 'var(--color-brand)';
+ });
+ btn.addEventListener('mouseout', () => {
+ btn.style.borderColor = btn.dataset.borderColor;
+ });
+ });
}
function selectInvoiceProduct(lookupKey, productName, amountCents) {
selectedInvoiceProduct = { lookupKey, productName, amountCents };
- const price = new Intl.NumberFormat('en-US', {
+ const hasDiscount = orgData?.discount_percent || orgData?.discount_amount_cents;
+ const netPrice = hasDiscount ? calculateDiscountedPrice(amountCents) : amountCents;
+
+ const listPriceFormatted = new Intl.NumberFormat('en-US', {
style: 'currency',
- currency: 'USD'
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0
}).format(amountCents / 100);
+ const netPriceFormatted = new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ minimumFractionDigits: 0,
+ maximumFractionDigits: 0
+ }).format(netPrice / 100);
+
+ let priceDisplay;
+ if (hasDiscount && amountCents !== netPrice) {
+ priceDisplay = `
+
${listPriceFormatted}
+
${netPriceFormatted}
+ `;
+ } else {
+ priceDisplay = `
${listPriceFormatted}`;
+ }
+
document.getElementById('selectedProductDisplay').innerHTML = `
-
${productName} -
${price}
+
${productName} - ${priceDisplay}
`;
document.getElementById('invoiceProductSection').style.display = 'none';
document.getElementById('invoiceBillingSection').style.display = 'block';
- document.getElementById('sendInvoiceBtn').style.display = 'inline-block';
+ document.getElementById('invoiceDraftReview').style.display = 'none';
+ document.getElementById('prepareDraftBtn').style.display = 'inline-block';
+ document.getElementById('sendInvoiceBtn').style.display = 'none';
+ document.getElementById('backToEditBtn').style.display = 'none';
+ }
+
+ function prepareDraft() {
+ // Collect all invoice data for review
+ const contactName = document.getElementById('invoiceContactName').value;
+ const contactEmail = document.getElementById('invoiceContactEmail').value;
+ const companyName = document.getElementById('invoiceCompanyName').value;
+ const addressLine1 = document.getElementById('invoiceAddressLine1').value;
+ const addressLine2 = document.getElementById('invoiceAddressLine2').value;
+ const city = document.getElementById('invoiceCity').value;
+ const state = document.getElementById('invoiceState').value;
+ const postalCode = document.getElementById('invoicePostalCode').value;
+ const country = document.getElementById('invoiceCountry').value;
+
+ // Check for missing required fields
+ const missingFields = [];
+ if (!contactName) missingFields.push('Contact Name');
+ if (!contactEmail) missingFields.push('Contact Email');
+ if (!companyName) missingFields.push('Company Name');
+ if (!addressLine1) missingFields.push('Street Address');
+ if (!city) missingFields.push('City');
+ if (!state) missingFields.push('State/Province');
+ if (!postalCode) missingFields.push('Postal Code');
+ if (!country) missingFields.push('Country');
+
+ // Calculate pricing
+ const hasDiscount = orgData?.discount_percent || orgData?.discount_amount_cents;
+ const listPrice = selectedInvoiceProduct.amountCents;
+ const netPrice = hasDiscount ? calculateDiscountedPrice(listPrice) : listPrice;
+
+ const listPriceFormatted = new Intl.NumberFormat('en-US', {
+ style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0
+ }).format(listPrice / 100);
+
+ const netPriceFormatted = new Intl.NumberFormat('en-US', {
+ style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0
+ }).format(netPrice / 100);
+
+ // Build discount info
+ let discountHtml = '';
+ if (hasDiscount && listPrice !== netPrice) {
+ const discountText = orgData.discount_percent
+ ? `${orgData.discount_percent}% discount`
+ : `${formatPriceCurrency(orgData.discount_amount_cents)} off`;
+ const savingsFormatted = new Intl.NumberFormat('en-US', {
+ style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0
+ }).format((listPrice - netPrice) / 100);
+ discountHtml = `
+
+
+ ${discountText} applied (saves ${savingsFormatted})
+
+
+ `;
+ }
+
+ // Build address display
+ const addressParts = [addressLine1];
+ if (addressLine2) addressParts.push(addressLine2);
+ addressParts.push(`${city}, ${state} ${postalCode}`);
+ addressParts.push(country);
+ const fullAddress = addressParts.filter(Boolean).join('
');
+
+ // Populate review content
+ document.getElementById('draftReviewContent').innerHTML = `
+
+
+
Company
+
${escapeHtml(companyName) || 'Not provided'}
+
+
+
Contact
+
${escapeHtml(contactName) || 'Not provided'}
+
${escapeHtml(contactEmail) || 'Not provided'}
+
+
+
+
+
+
+
+
Product
+
${escapeHtml(selectedInvoiceProduct.productName)}
+
+
+
Amount
+ ${hasDiscount && listPrice !== netPrice
+ ? `
${listPriceFormatted} ${netPriceFormatted}
`
+ : `
${listPriceFormatted}
`
+ }
+ ${discountHtml}
+
+
+
+
+
+
+
Billing Address
+
${fullAddress || 'Not provided'}
+
+ `;
+
+ // Show/hide missing fields warning
+ const missingFieldsDiv = document.getElementById('draftMissingFields');
+ const missingFieldsList = document.getElementById('missingFieldsList');
+ if (missingFields.length > 0) {
+ missingFieldsList.innerHTML = missingFields.map(f => `
${f}`).join('');
+ missingFieldsDiv.style.display = 'block';
+ } else {
+ missingFieldsDiv.style.display = 'none';
+ }
+
+ // Update UI state
+ document.getElementById('invoiceBillingSection').style.display = 'none';
+ document.getElementById('invoiceDraftReview').style.display = 'block';
+ document.getElementById('prepareDraftBtn').style.display = 'none';
+ document.getElementById('backToEditBtn').style.display = 'inline-block';
+ document.getElementById('sendInvoiceBtn').style.display = missingFields.length === 0 ? 'inline-block' : 'none';
+ }
+
+ function backToEditInvoice() {
+ document.getElementById('invoiceDraftReview').style.display = 'none';
+ document.getElementById('invoiceBillingSection').style.display = 'block';
+ document.getElementById('prepareDraftBtn').style.display = 'inline-block';
+ document.getElementById('backToEditBtn').style.display = 'none';
+ document.getElementById('sendInvoiceBtn').style.display = 'none';
}
async function submitInvoice(event) {
@@ -3468,6 +3765,11 @@
Set Discount
}
};
+ // Include coupon_id if the org has a discount configured
+ if (orgData?.stripe_coupon_id) {
+ data.coupon_id = orgData.stripe_coupon_id;
+ }
+
try {
const response = await fetch(`/api/admin/prospects/${orgId}/invoice`, {
method: 'POST',
@@ -3484,7 +3786,9 @@
Set Discount
if (result.success && result.invoice_url) {
document.getElementById('invoiceBillingSection').style.display = 'none';
+ document.getElementById('invoiceDraftReview').style.display = 'none';
document.getElementById('sendInvoiceBtn').style.display = 'none';
+ document.getElementById('backToEditBtn').style.display = 'none';
document.getElementById('invoiceUrl').value = result.invoice_url;
document.getElementById('invoiceResult').style.display = 'block';
diff --git a/server/public/dashboard.html b/server/public/dashboard.html
index 2c432dbdda..26fbb00a38 100644
--- a/server/public/dashboard.html
+++ b/server/public/dashboard.html
@@ -1613,6 +1613,11 @@
Join the Agentic Advertising Organization
const customerType = org.is_personal ? 'individual' : 'company';
const revenueTier = org.revenue_tier || '';
+ // Get discount info from billing data
+ const discountPercent = org.billing?.discount_percent || null;
+ const discountAmountCents = org.billing?.discount_amount_cents || null;
+ const hasDiscount = discountPercent || discountAmountCents;
+
// Fetch products filtered by customer type and revenue tier
const url = new URL('/api/billing-products', window.location.origin);
url.searchParams.set('customer_type', customerType);
@@ -1637,25 +1642,70 @@
Join the Agentic Advertising Organization
return;
}
+ // Helper to calculate discounted price
+ function calculateDiscountedPrice(amountCents) {
+ if (discountPercent) {
+ return Math.round(amountCents * (1 - discountPercent / 100));
+ } else if (discountAmountCents) {
+ return Math.max(0, amountCents - discountAmountCents);
+ }
+ return amountCents;
+ }
+
+ // Build discount banner if applicable
+ let discountBanner = '';
+ if (hasDiscount) {
+ const discountText = discountPercent
+ ? `${discountPercent}% discount`
+ : formatCurrency(discountAmountCents) + ' discount';
+ discountBanner = `
+
+
+ Your organization has a ${discountText} applied to membership
+
+
+ `;
+ }
+
// Render product cards
const productCards = products.map(product => {
- const price = formatCurrency(product.amount_cents, product.currency);
+ const listPrice = product.amount_cents;
+ const netPrice = hasDiscount ? calculateDiscountedPrice(listPrice) : listPrice;
+ const listPriceFormatted = formatCurrency(listPrice, product.currency);
+ const netPriceFormatted = formatCurrency(netPrice, product.currency);
const interval = product.billing_interval === 'year' ? '/year' : product.billing_interval === 'month' ? '/month' : '';
const isSubscription = product.billing_type === 'subscription';
- return `
-
-
${escapeHtml(product.display_name)}
- ${product.description ? `
${escapeHtml(product.description)}
` : ''}
+ // Build price display
+ let priceDisplay;
+ if (hasDiscount && listPrice !== netPrice) {
+ priceDisplay = `
-
${price}
+
+ ${listPriceFormatted}
+
+
${netPriceFormatted}
${interval}
+ `;
+ } else {
+ priceDisplay = `
+
+ ${listPriceFormatted}
+ ${interval}
+
+ `;
+ }
+
+ return `
+
+
${escapeHtml(product.display_name)}
+ ${product.description ? `
${escapeHtml(product.description)}
` : ''}
+ ${priceDisplay}
`;
+
+ // Add event listeners to avoid XSS from inline handlers
+ container.querySelectorAll('.product-card').forEach(card => {
+ card.addEventListener('mouseover', () => {
+ card.style.borderColor = 'var(--aao-primary)';
+ card.style.boxShadow = '0 4px 12px rgba(102, 126, 234, 0.15)';
+ });
+ card.addEventListener('mouseout', () => {
+ card.style.borderColor = 'var(--color-border)';
+ card.style.boxShadow = 'none';
+ });
+ });
+ container.querySelectorAll('.checkout-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ startCheckout(btn.dataset.priceId, btn.dataset.orgId);
+ });
+ });
} catch (error) {
console.error('Error loading products:', error);
container.innerHTML = `
@@ -2673,6 +2741,10 @@
Create Your Pro
is_personal: billingData.is_personal,
suggested_company_type: billingData.suggested_company_type,
suggested_revenue_tier: billingData.suggested_revenue_tier,
+ // Discount info
+ discount_percent: billingData.discount_percent,
+ discount_amount_cents: billingData.discount_amount_cents,
+ stripe_promotion_code: billingData.stripe_promotion_code,
},
stripeCustomerId: billingData.stripe_customer_id,
customerSessionSecret: billingData.customer_session_secret,
@@ -3303,6 +3375,385 @@ Create Your Pro
window.history.replaceState({}, '', newUrl);
}
});
+
+ // =========================================================================
+ // Invoice Request Modal Functions (Two-Step Draft Process)
+ // =========================================================================
+
+ let invoiceProducts = [];
+ let selectedInvoiceProductData = null;
+
+ function openInvoiceRequestModal(options = {}) {
+ const modal = document.getElementById('invoiceRequestModal');
+ const productSelect = document.getElementById('invoice-product');
+
+ // Reset modal state
+ document.getElementById('invoiceFormSection').style.display = 'block';
+ document.getElementById('invoiceDraftReviewSection').style.display = 'none';
+ document.getElementById('invoiceSuccessSection').style.display = 'none';
+ document.getElementById('invoiceFormError').style.display = 'none';
+ document.getElementById('invoiceRequestForm').reset();
+ document.getElementById('prepareDraftInvoiceBtn').style.display = 'inline-block';
+ document.getElementById('backToEditInvoiceBtn').style.display = 'none';
+ document.getElementById('sendInvoiceRequestBtn').style.display = 'none';
+
+ // Pre-fill company name if provided
+ if (options.companyName) {
+ document.getElementById('invoice-company').value = options.companyName;
+ }
+
+ // Load products and populate dropdown
+ loadInvoiceProducts(options.selectedProduct);
+
+ modal.style.display = 'flex';
+ document.getElementById('invoice-company').focus();
+ }
+
+ async function loadInvoiceProducts(preSelectedKey) {
+ const productSelect = document.getElementById('invoice-product');
+
+ try {
+ const response = await fetch('/api/products');
+ if (!response.ok) throw new Error('Failed to load products');
+
+ const data = await response.json();
+ invoiceProducts = (data.products || []).filter(p => p.billing_type === 'subscription' && p.is_invoiceable);
+
+ // Sort by price
+ invoiceProducts.sort((a, b) => (a.amount_cents || 0) - (b.amount_cents || 0));
+
+ // Check if org has discount
+ const hasDiscount = currentOrg?.billing?.discount_percent || currentOrg?.billing?.discount_amount_cents;
+
+ productSelect.innerHTML = '';
+ invoiceProducts.forEach(product => {
+ const listPrice = product.amount_cents || 0;
+ const netPrice = hasDiscount ? calculateInvoiceDiscountedPrice(listPrice) : listPrice;
+ const priceText = hasDiscount && netPrice !== listPrice
+ ? `${formatInvoiceCurrency(netPrice)}/year (was ${formatInvoiceCurrency(listPrice)})`
+ : `${formatInvoiceCurrency(listPrice)}/year`;
+ productSelect.innerHTML += ``;
+ });
+
+ // Pre-select if specified
+ if (preSelectedKey) {
+ productSelect.value = preSelectedKey;
+ }
+ } catch (error) {
+ console.error('Error loading products:', error);
+ productSelect.innerHTML = '';
+ }
+ }
+
+ function calculateInvoiceDiscountedPrice(amountCents) {
+ if (currentOrg?.billing?.discount_percent) {
+ return Math.round(amountCents * (1 - currentOrg.billing.discount_percent / 100));
+ } else if (currentOrg?.billing?.discount_amount_cents) {
+ return Math.max(0, amountCents - currentOrg.billing.discount_amount_cents);
+ }
+ return amountCents;
+ }
+
+ function formatInvoiceCurrency(cents) {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency', currency: 'USD', minimumFractionDigits: 0, maximumFractionDigits: 0
+ }).format(cents / 100);
+ }
+
+ function closeInvoiceRequestModal() {
+ document.getElementById('invoiceRequestModal').style.display = 'none';
+ selectedInvoiceProductData = null;
+ }
+
+ function prepareDraftInvoice() {
+ // Collect form data
+ const companyName = document.getElementById('invoice-company').value;
+ const contactName = document.getElementById('invoice-contact').value;
+ const contactEmail = document.getElementById('invoice-email').value;
+ const lookupKey = document.getElementById('invoice-product').value;
+ const addressLine1 = document.getElementById('invoice-address1').value;
+ const addressLine2 = document.getElementById('invoice-address2').value;
+ const city = document.getElementById('invoice-city').value;
+ const state = document.getElementById('invoice-state').value;
+ const postalCode = document.getElementById('invoice-postal').value;
+ const country = document.getElementById('invoice-country').value;
+
+ // Find selected product
+ const product = invoiceProducts.find(p => p.lookup_key === lookupKey);
+ if (!product) {
+ document.getElementById('invoiceFormError').textContent = 'Please select a product';
+ document.getElementById('invoiceFormError').style.display = 'block';
+ return;
+ }
+
+ // Check for missing required fields
+ const missingFields = [];
+ if (!companyName) missingFields.push('Company Name');
+ if (!contactName) missingFields.push('Contact Name');
+ if (!contactEmail) missingFields.push('Billing Email');
+ if (!addressLine1) missingFields.push('Address Line 1');
+ if (!city) missingFields.push('City');
+ if (!state) missingFields.push('State/Province');
+ if (!postalCode) missingFields.push('Postal Code');
+ if (!country) missingFields.push('Country');
+
+ // Store selected product data
+ selectedInvoiceProductData = { product, lookupKey };
+
+ // Calculate pricing
+ const hasDiscount = currentOrg?.billing?.discount_percent || currentOrg?.billing?.discount_amount_cents;
+ const listPrice = product.amount_cents || 0;
+ const netPrice = hasDiscount ? calculateInvoiceDiscountedPrice(listPrice) : listPrice;
+
+ // Build discount info
+ let discountHtml = '';
+ if (hasDiscount && listPrice !== netPrice) {
+ const discountText = currentOrg.billing.discount_percent
+ ? `${currentOrg.billing.discount_percent}% discount`
+ : `${formatInvoiceCurrency(currentOrg.billing.discount_amount_cents)} off`;
+ const savings = formatInvoiceCurrency(listPrice - netPrice);
+ discountHtml = `
+
+ ${discountText} applied (saves ${savings})
+
+ `;
+ }
+
+ // Build address display
+ const addressParts = [addressLine1];
+ if (addressLine2) addressParts.push(addressLine2);
+ addressParts.push(`${city}, ${state} ${postalCode}`);
+ addressParts.push(country);
+ const fullAddress = addressParts.filter(Boolean).join('
');
+
+ // Populate draft review content
+ document.getElementById('invoiceDraftReviewContent').innerHTML = `
+
+
+
Company
+
${escapeHtml(companyName) || 'Not provided'}
+
+
+
Contact
+
${escapeHtml(contactName) || 'Not provided'}
+
${escapeHtml(contactEmail) || 'Not provided'}
+
+
+
+
+
+
+
+
Product
+
${escapeHtml(product.display_name)}
+
+
+
Amount
+ ${hasDiscount && listPrice !== netPrice
+ ? `
${formatInvoiceCurrency(listPrice)} ${formatInvoiceCurrency(netPrice)}/year
`
+ : `
${formatInvoiceCurrency(listPrice)}/year
`
+ }
+ ${discountHtml}
+
+
+
+
+
+
+
Billing Address
+
${fullAddress || 'Not provided'}
+
+ `;
+
+ // Show/hide missing fields warning
+ const missingFieldsDiv = document.getElementById('invoiceMissingFields');
+ if (missingFields.length > 0) {
+ missingFieldsDiv.innerHTML = `Missing Information: ${missingFields.join(', ')}`;
+ missingFieldsDiv.style.display = 'block';
+ } else {
+ missingFieldsDiv.style.display = 'none';
+ }
+
+ // Update UI state
+ document.getElementById('invoiceFormSection').style.display = 'none';
+ document.getElementById('invoiceDraftReviewSection').style.display = 'block';
+ document.getElementById('prepareDraftInvoiceBtn').style.display = 'none';
+ document.getElementById('backToEditInvoiceBtn').style.display = 'inline-block';
+ document.getElementById('sendInvoiceRequestBtn').style.display = missingFields.length === 0 ? 'inline-block' : 'none';
+ }
+
+ function backToEditInvoiceRequest() {
+ document.getElementById('invoiceDraftReviewSection').style.display = 'none';
+ document.getElementById('invoiceFormSection').style.display = 'block';
+ document.getElementById('prepareDraftInvoiceBtn').style.display = 'inline-block';
+ document.getElementById('backToEditInvoiceBtn').style.display = 'none';
+ document.getElementById('sendInvoiceRequestBtn').style.display = 'none';
+ }
+
+ async function submitInvoiceRequest(event) {
+ event.preventDefault();
+
+ const submitBtn = document.getElementById('sendInvoiceRequestBtn');
+ const errorDiv = document.getElementById('invoiceFormError');
+
+ submitBtn.disabled = true;
+ submitBtn.textContent = 'Sending...';
+ errorDiv.style.display = 'none';
+
+ const form = document.getElementById('invoiceRequestForm');
+ const formData = new FormData(form);
+
+ const requestData = {
+ companyName: formData.get('companyName'),
+ contactName: formData.get('contactName'),
+ contactEmail: formData.get('contactEmail'),
+ lookupKey: formData.get('lookupKey'),
+ billingAddress: {
+ line1: formData.get('line1'),
+ line2: formData.get('line2') || undefined,
+ city: formData.get('city'),
+ state: formData.get('state'),
+ postal_code: formData.get('postal_code'),
+ country: formData.get('country'),
+ },
+ };
+
+ try {
+ const response = await fetch('/api/invoice-request', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify(requestData),
+ });
+
+ const result = await response.json();
+
+ if (!response.ok) {
+ throw new Error(result.message || 'Failed to send invoice');
+ }
+
+ // Show success state
+ document.getElementById('invoiceDraftReviewSection').style.display = 'none';
+ document.getElementById('backToEditInvoiceBtn').style.display = 'none';
+ document.getElementById('sendInvoiceRequestBtn').style.display = 'none';
+
+ document.getElementById('invoiceSuccessSection').innerHTML = `
+
+
ā
+
Invoice Sent!
+
We've sent an invoice to ${escapeHtml(requestData.contactEmail)}. Please check your email for payment instructions.
+
The invoice is due within 30 days. You can pay online via the link in the email.
+
+ `;
+ document.getElementById('invoiceSuccessSection').style.display = 'block';
+
+ } catch (error) {
+ errorDiv.textContent = error.message || 'Something went wrong. Please try again or contact finance@agenticadvertising.org';
+ errorDiv.style.display = 'block';
+ } finally {
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Send Invoice';
+ }
+ }
+
+
+
+
+
+
Request an Invoice
+
+
+
+
+