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:
1 parent
7799d7c4de
commit
e5cc57cefd
7 files changed
+402
-35
No files matched your search
@@ -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)
|
||||
})
|
||||
Reference in new issue
Block a user