Abo-API: Bestellung je Produkt, Katalog, Speicherstand, Wechsel-Möglichkeit

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-28 09:43:09 +00:00
1 parent 05363d06ad
commit c4607e9c43
6 files changed
+784 -40

No files matched your search

+153
View File
@@ -0,0 +1,153 @@
// testApp.js zuerst: es prueft beim Laden, dass useTestEnv() gelaufen ist, bevor config.js die Umgebung liest
import { sessionCookie } from './testApp.js'
import { config } from '../../server/config.js'
/**
* Feste Testdaten für die Routen-Tests (Aufgaben 7–13): Kunden c1–c3, Abo-Dokumente, Zustimmung,
* Session-Cookies. seedFixtures(aw) legt Kunden und Abos in den Appwrite-Nachbau; c3 hat kein Abo
* (Neuabschluss-Tests). Erst nach useTestEnv() dynamisch importieren (siehe testApp.js).
*/
const DAY_MS = 86400000
const daysFromNow = (days) => new Date(Date.now() + days * DAY_MS).toISOString()
export const customers = {
c1: { $id: 'c1', appwriteUserId: 'u1', email: 'k@example.invalid', name: 'Kunde Eins', status: 'active', portalAccessEnabled: true },
c2: { $id: 'c2', appwriteUserId: 'u2', email: 'k2@example.invalid', name: 'Kunde Zwei', status: 'active', portalAccessEnabled: true },
c3: { $id: 'c3', appwriteUserId: 'u3', email: 'k3@example.invalid', name: 'Kunde Drei', status: 'active', portalAccessEnabled: true },
}
// Abo seit 40 Tagen (Widerrufsfrist des Abschlusses vorbei), naechste Abbuchung in 20 Tagen
const startedAt = daysFromNow(-40)
/** Hosting-Abo sub_h von c1: aktiv, Fassung 4, ohne Zusatzspeicher. */
export const hostingDoc = {
$id: 'sub_h',
customerId: 'c1',
email: 'k@example.invalid',
stripeCustomerId: 'cus_c1',
stripeSubscriptionId: 'sub_h',
status: 'active',
productKey: 'hosting',
planKey: config.products.hosting.lookupKey,
priceAmount: config.products.hosting.amountCents,
startedAt,
termStartAt: startedAt,
currentPeriodEnd: daysFromNow(20),
trialEnd: null,
cancelAtPeriodEnd: false,
cancelAt: null,
canceledAt: null,
endedAt: null,
termsVersion: config.abo.termsVersion,
termsAcceptedAt: startedAt,
earlyStartConsentAt: startedAt,
startChoice: 'sofort',
consentIp: '127.0.0.1',
consentUserAgent: 'test',
consentTextVersion: config.abo.termsVersion,
storageUnits: 0,
storagePaidUnits: 0,
storagePaidPeriodEnd: null,
}
/** Wartungs-Abo sub_w von c1 (im Test meist als sub_h eingespielt, damit c1 nur ein Abo hat). */
export const wartungDoc = {
...hostingDoc,
$id: 'sub_w',
stripeSubscriptionId: 'sub_w',
productKey: 'wartung',
planKey: config.products.wartung.lookupKey,
priceAmount: config.products.wartung.amountCents,
}
/** Zweites Hosting-Abo sub_h2 von c2. */
export const hosting2Doc = {
...hostingDoc,
$id: 'sub_h2',
customerId: 'c2',
email: 'k2@example.invalid',
stripeCustomerId: 'cus_c2',
stripeSubscriptionId: 'sub_h2',
}
const consentAt = new Date().toISOString()
/** Zustimmung im Portal (Bestellung, Wechsel, Speicher-Plus); earlyStartConsentAt fuer den Wechsel (F16). */
export const consent = {
termsVersion: config.abo.termsVersion,
consentAt,
consentIp: '127.0.0.1',
consentUserAgent: 'test',
earlyStartConsentAt: consentAt,
}
/** Kunden-Session; Standard c1. */
export const customerCookie = (customerId = 'c1') =>
sessionCookie({ customerId, appwriteUserId: customers[customerId]?.appwriteUserId || `u_${customerId}` })
/** Admin-Session (Ticketsystem-Mitarbeiter im Portal), F11. */
export const adminCookie = () =>
sessionCookie({ role: 'admin', appwriteUserId: 'u_admin', name: 'Kenso', email: 'kenso@webklar.com' })
/** Kunden c1–c3 und die Abos sub_h (c1) und sub_h2 (c2) in den Appwrite-Nachbau legen. */
export function seedFixtures(aw) {
for (const customer of Object.values(customers)) aw.seed('customers', customer)
aw.seed('subscriptions', hostingDoc)
aw.seed('subscriptions', hosting2Doc)
}
const unix = (iso) => Math.floor(new Date(iso).getTime() / 1000)
/**
* Stripe-Abo in der Form der SDK-API-Version für den Stripe-Nachbau: Abo-Posten je Produkt,
* dazu ein Zusatzspeicher-Posten, wenn storageUnits > 0. metadata ergaenzt/ueberschreibt die
* Zustimmungsdaten, rest alles Weitere (status, cancel_at, ...).
*/
export function stripeSubscription({ id = 'sub_h', customerId = 'c1', productKey = 'hosting', storageUnits = 0, metadata = {}, ...rest } = {}) {
const product = config.products[productKey]
const items = [
{
id: `si_${id}_abo`,
object: 'subscription_item',
quantity: 1,
price: { id: `price_${productKey}`, object: 'price', lookup_key: product.lookupKey, unit_amount: product.amountCents },
},
]
if (storageUnits > 0) {
items.push({
id: `si_${id}_speicher`,
object: 'subscription_item',
quantity: storageUnits,
price: { id: 'price_mailStorage', object: 'price', lookup_key: config.mailStorage.lookupKey, unit_amount: config.mailStorage.amountCents },
})
}
return {
id,
object: 'subscription',
status: 'active',
customer: `cus_${customerId}`,
start_date: unix(hostingDoc.startedAt),
current_period_end: unix(hostingDoc.currentPeriodEnd),
trial_end: null,
cancel_at_period_end: false,
cancel_at: null,
canceled_at: null,
ended_at: null,
metadata: {
webklarCustomerId: customerId,
email: customers[customerId]?.email || '',
productKey,
termsVersion: config.abo.termsVersion,
consentTextVersion: config.abo.termsVersion,
termsAcceptedAt: consentAt,
startChoice: 'sofort',
earlyStartConsentAt: consentAt,
consentIp: '127.0.0.1',
consentUserAgent: 'test',
...metadata,
},
items: { object: 'list', data: items },
...rest,
}
}