Stripe: Preise je Produkt, Testrabatt Hosting, Kundenportal „Abo verwalten“, Mindestlaufzeit ab termStartAt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-28 09:21:25 +00:00
1 parent 7799d7c4de
commit e5cc57cefd
7 files changed
+402 -35

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
#!/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)
})