79 lines
3.1 KiB
JavaScript
79 lines
3.1 KiB
JavaScript
import { config } from '../config.js'
|
|
|
|
/**
|
|
* Abo-Produkte (Hosting-Abo, Wartungs-Abo) und E-Mail-Zusatzspeicher.
|
|
*
|
|
* Reine Funktionen ohne Netzwerk: Sie ordnen Stripe-Preise (lookup_key) den Produkten zu und
|
|
* werten Abo-Positionen und Rechnungszeilen aus. Stripe-Objekte haben die Form der API-Version
|
|
* des SDK (2025-10-29.clover).
|
|
*/
|
|
|
|
export const PRODUCT_KEYS = Object.freeze(['wartung', 'hosting'])
|
|
|
|
function httpError(status, message) {
|
|
const err = new Error(message)
|
|
err.status = status
|
|
return err
|
|
}
|
|
|
|
/** Produkt aus dem Katalog; unbekannter Schlüssel -> 400. */
|
|
export function getProduct(key) {
|
|
const product = PRODUCT_KEYS.includes(key) ? config.products[key] : null
|
|
if (!product) throw httpError(400, `Unbekanntes Abo: ${key}`)
|
|
return product
|
|
}
|
|
|
|
/** lookup_key eines Stripe-Preises -> 'wartung' | 'hosting' | 'mailStorage' | null */
|
|
export function productKeyForLookupKey(lookupKey) {
|
|
if (!lookupKey) return null
|
|
const product = PRODUCT_KEYS.find((key) => config.products[key].lookupKey === lookupKey)
|
|
if (product) return product
|
|
return config.mailStorage.lookupKey === lookupKey ? 'mailStorage' : null
|
|
}
|
|
|
|
/**
|
|
* Positionen eines Stripe-Abos (subscription.items.data) auswerten. Der Abo-Posten ist die
|
|
* erste Position mit einem Hosting- oder Wartungs-Preis, unabhängig von der Reihenfolge.
|
|
*/
|
|
export function describeItems(items) {
|
|
const list = Array.isArray(items) ? items : []
|
|
const abo = list.find((item) => PRODUCT_KEYS.includes(productKeyForLookupKey(item?.price?.lookup_key)))
|
|
const storage = list.find((item) => productKeyForLookupKey(item?.price?.lookup_key) === 'mailStorage')
|
|
return {
|
|
productKey: abo ? productKeyForLookupKey(abo.price.lookup_key) : null,
|
|
aboItemId: abo?.id || null,
|
|
aboPriceId: abo?.price?.id || null,
|
|
lookupKey: abo?.price?.lookup_key || null,
|
|
priceAmount: abo?.price?.unit_amount ?? null,
|
|
storageItemId: storage?.id || null,
|
|
storageUnits: storage ? Number(storage.quantity) || 0 : 0,
|
|
}
|
|
}
|
|
|
|
const priceIdOfLine = (line) => line?.pricing?.price_details?.price || null
|
|
|
|
/** Preis-IDs aller Rechnungszeilen, jede nur einmal. */
|
|
export function priceIdsOfInvoice(invoice) {
|
|
const ids = (invoice?.lines?.data || []).map(priceIdOfLine).filter(Boolean)
|
|
return [...new Set(ids)]
|
|
}
|
|
|
|
/**
|
|
* Arbeitszeit, die eine bezahlte Rechnung gutschreibt: die Minuten des Produkts der ersten
|
|
* regulären (nicht anteiligen) Abo-Zeile über 0 €, deren Produkt Arbeitszeit enthält. Anteilige
|
|
* Verrechnungen, Probezeiten (0 €), Hosting, Zusatzspeicher und unbekannte Preise zählen nicht.
|
|
* priceKeys: Map Preis-ID -> 'wartung' | 'hosting' | 'mailStorage' | null
|
|
*/
|
|
export function creditMinutesForInvoice(invoice, priceKeys) {
|
|
for (const line of invoice?.lines?.data || []) {
|
|
const key = priceKeys.get(priceIdOfLine(line))
|
|
if (!PRODUCT_KEYS.includes(key)) continue
|
|
const minutes = config.products[key].minutesPerMonth
|
|
const regular =
|
|
line.parent?.type === 'subscription_item_details' &&
|
|
line.parent.subscription_item_details?.proration === false
|
|
if (minutes > 0 && regular && line.amount > 0) return minutes
|
|
}
|
|
return 0
|
|
}
|