104 lines
4.5 KiB
JavaScript
104 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Stripe-Preise je Produkt (Wartungs-Abo, Hosting-Abo, E-Mail-Zusatzspeicher): legt fehlende
|
||
* Preise an (ensurePrice), haelt die Wartungs-Produktbeschreibung bei Stripe auf dem Katalogtext
|
||
* (server/config.js) und passt die Kundenportal-Konfiguration an ("WEBklar – Abo verwalten").
|
||
* Idempotent: vorhandene Preise werden ueber lookup_key gefunden, nicht neu angelegt.
|
||
*
|
||
* Usage (im Container):
|
||
* node scripts/stripe-products.mjs --pruefen (nur anzeigen, was fehlt oder abweicht)
|
||
* node scripts/stripe-products.mjs (anlegen/anpassen)
|
||
*/
|
||
import 'dotenv/config'
|
||
import { config } from '../server/config.js'
|
||
import { ensurePortalConfiguration, ensurePrice, getStripe, PORTAL_HEADLINE } from '../server/services/stripeBilling.js'
|
||
import { PRODUCT_KEYS } from '../server/services/products.js'
|
||
|
||
const KEYS = [...PRODUCT_KEYS, 'mailStorage'] // ['wartung', 'hosting', 'mailStorage']
|
||
|
||
function catalogEntry(key) {
|
||
return key === 'mailStorage' ? config.mailStorage : config.products[key]
|
||
}
|
||
|
||
async function findPrice(lookupKey) {
|
||
const { data } = await getStripe().prices.list({ lookup_keys: [lookupKey], active: true, limit: 1 })
|
||
return data[0] || null
|
||
}
|
||
|
||
async function checkPrice(key) {
|
||
const catalog = catalogEntry(key)
|
||
const price = await findPrice(catalog.lookupKey)
|
||
if (!price) return { ok: false, note: `Preis ${catalog.lookupKey} fehlt bei Stripe` }
|
||
const mismatches = []
|
||
if (price.unit_amount !== catalog.amountCents) mismatches.push(`Betrag ${price.unit_amount} statt ${catalog.amountCents}`)
|
||
if (price.recurring?.interval !== 'month') mismatches.push(`recurring.interval "${price.recurring?.interval}" statt "month"`)
|
||
if (price.tax_behavior !== 'inclusive') mismatches.push(`tax_behavior "${price.tax_behavior}" statt "inclusive"`)
|
||
return { ok: mismatches.length === 0, note: mismatches.length ? mismatches.join('; ') : `OK (${price.id})` }
|
||
}
|
||
|
||
async function checkWartungDescription() {
|
||
const price = await findPrice(config.products.wartung.lookupKey)
|
||
if (!price) return { ok: false, note: 'Preis fehlt, Produkt kann nicht geprüft werden' }
|
||
const productId = typeof price.product === 'string' ? price.product : price.product.id
|
||
const product = await getStripe().products.retrieve(productId)
|
||
const ok = product.description === config.products.wartung.description
|
||
return { ok, note: ok ? 'OK' : 'Produktbeschreibung weicht vom Katalogtext ab' }
|
||
}
|
||
|
||
async function checkPortalConfig() {
|
||
const { data } = await getStripe().billingPortal.configurations.list({ active: true, limit: 20 })
|
||
const own = data.find((c) => c.metadata?.webklar === 'wartungs-abo')
|
||
if (!own) return { ok: false, note: 'Konfiguration fehlt bei Stripe' }
|
||
const ok = own.business_profile?.headline === PORTAL_HEADLINE
|
||
return { ok, note: ok ? 'OK' : `Überschrift "${own.business_profile?.headline}" statt "${PORTAL_HEADLINE}"` }
|
||
}
|
||
|
||
async function pruefen() {
|
||
let allOk = true
|
||
for (const key of KEYS) {
|
||
const r = await checkPrice(key)
|
||
allOk = allOk && r.ok
|
||
console.log(`${r.ok ? '✅' : '❌'} Preis ${key}: ${r.note}`)
|
||
}
|
||
const desc = await checkWartungDescription()
|
||
allOk = allOk && desc.ok
|
||
console.log(`${desc.ok ? '✅' : '❌'} Wartung-Produktbeschreibung: ${desc.note}`)
|
||
const portal = await checkPortalConfig()
|
||
allOk = allOk && portal.ok
|
||
console.log(`${portal.ok ? '✅' : '❌'} Kundenportal-Konfiguration: ${portal.note}`)
|
||
if (!allOk) {
|
||
console.log('')
|
||
console.log('Zum Anlegen/Anpassen: node scripts/stripe-products.mjs')
|
||
}
|
||
}
|
||
|
||
async function anlegen() {
|
||
for (const key of KEYS) {
|
||
const priceId = await ensurePrice(key)
|
||
console.log(`✅ Preis ${key}: ${priceId}`)
|
||
}
|
||
const price = await findPrice(config.products.wartung.lookupKey)
|
||
const productId = typeof price.product === 'string' ? price.product : price.product.id
|
||
await getStripe().products.update(productId, { description: config.products.wartung.description })
|
||
console.log('✅ Wartung-Produktbeschreibung auf Katalogtext gesetzt')
|
||
const configurationId = await ensurePortalConfiguration()
|
||
console.log(`✅ Kundenportal-Konfiguration: ${configurationId}`)
|
||
}
|
||
|
||
async function main() {
|
||
if (!config.stripe.secretKey) {
|
||
console.error('❌ STRIPE_SECRET_KEY fehlt')
|
||
process.exit(1)
|
||
}
|
||
const checkOnly = process.argv.includes('--pruefen')
|
||
console.log(checkOnly ? 'Modus: --pruefen (nur lesen, keine Änderungen)' : 'Modus: anlegen/anpassen')
|
||
console.log('')
|
||
if (checkOnly) await pruefen()
|
||
else await anlegen()
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('❌', err.message)
|
||
process.exit(1)
|
||
})
|