Files
Webklar-Kundenbereich/scripts/wechsel-gescheitert.mjs
T

78 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Wechsel ins Wartungs-Abo als gescheitert abschließen, wenn die SEPA-Lastschrift der Wechselrechnung nicht
* eingelöst wurde (Vertragsbedingungen § 9 Abs. 3, Rechtsrunde 28.09.2026). Das Team bekommt dann T12 mit den
* Handgriffen: Wechselrechnung stornieren, Preis ohne Verrechnung zurück auf Hosting, Metadaten zurücksetzen.
* Danach setzt dieses Skript das Ereignis `wechsel` auf `gescheitert` und schickt K14 an den Kunden und T12
* (gescheitert) ans Team (markSwitchFailed in server/services/planSwitch.js, wie beim Webhook
* customer.subscription.pending_update_expired für Karten).
*
* Usage (im Container, als Datei):
* node scripts/wechsel-gescheitert.mjs <Stripe-Abo-ID> --pruefen (nur anzeigen: offener Wechsel,
* Metadaten vor dem Wechsel)
* node scripts/wechsel-gescheitert.mjs <Stripe-Abo-ID> --ausfuehren [Grund] (Ereignis gescheitert, K14, T12)
* Ohne Grund nennt K14: "Die SEPA-Lastschrift für die Wechselrechnung wurde nicht eingelöst."
*/
import 'dotenv/config'
import { config } from '../server/config.js'
import { getDocument } from '../server/services/appwriteAdmin.js'
import { findOpenSwitchEvent, markSwitchFailed, SWITCH_FAILED_REASONS } from '../server/services/planSwitch.js'
const USAGE = 'Usage: node scripts/wechsel-gescheitert.mjs <Stripe-Abo-ID> --pruefen | --ausfuehren [Grund]'
/** { subscriptionId, mode: 'pruefen' | 'ausfuehren', grund? } oder null bei falschem Aufruf. */
export function parseArgs(argv) {
const [subscriptionId, mode, ...rest] = argv.slice(2)
if (!/^sub_\w+$/.test(subscriptionId || '')) return null
if (mode === '--pruefen' && rest.length === 0) return { subscriptionId, mode: 'pruefen' }
if (mode === '--ausfuehren') return { subscriptionId, mode: 'ausfuehren', grund: rest.join(' ').trim() || SWITCH_FAILED_REASONS.lastschrift }
return null
}
/** Offenen Wechsel des Abos zeigen und (ausfuehren) als gescheitert abschließen. Liefert { ok, event? }. */
export async function run({ subscriptionId, mode, grund }, log = console.log) {
const sub = await getDocument(config.collections.subscriptions, subscriptionId).catch(() => null)
if (!sub) {
log(`❌ Abo ${subscriptionId} ist im Kundenportal nicht bekannt.`)
return { ok: false }
}
const event = await findOpenSwitchEvent(sub.customerId, subscriptionId)
if (!event) {
log(`❌ Kein offener Wechsel für Abo ${subscriptionId}.`)
return { ok: false }
}
const details = event.details || {}
const euro = `${(Number(details.amountDueCents || 0) / 100).toFixed(2).replace('.', ',')} €`
log(`Offener Wechsel ${event.$id} (Kunde ${event.customerId}, ${sub.email || 'ohne E-Mail'})`)
log(` Wechsel am ${details.switchedAt || '–'}, Zahlart ${details.paymentMethod || 'unbekannt'}, Rechnung ${details.invoiceId || '–'} über ${euro}`)
log(` Mindestlaufzeit des Hosting-Abos vor dem Wechsel ab ${details.previousTermStartAt || '–'}`)
log(`Metadaten am Stripe-Abo ${subscriptionId} auf den Stand vor dem Wechsel zurücksetzen:`)
for (const [key, value] of Object.entries(details.previousMetadata || {})) log(` ${key} = ${value ?? '(löschen)'}`)
if (mode === 'pruefen') {
log('')
log(`Nur geprüft, nichts geändert. Abschließen: node scripts/wechsel-gescheitert.mjs ${subscriptionId} --ausfuehren`)
return { ok: true, event }
}
const result = await markSwitchFailed(event.$id, grund, { doneBy: 'skript' })
log(`✅ Wechsel ${event.$id} ist gescheitert: K14 an den Kunden und T12 ans Team verschickt (Fehler stehen im Log).`)
return { ok: true, event: result.event }
}
async function main() {
const args = parseArgs(process.argv)
if (!args) {
console.error(`❌ ${USAGE}`)
process.exit(1)
}
const result = await run(args)
if (!result.ok) process.exitCode = 1
}
// Nur beim direkten Aufruf laufen lassen, nicht wenn ein Test das Modul importiert
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((err) => {
console.error('❌', err.message)
process.exit(1)
})
}