Abo-API: Nachweis-ID nach Appwrite-Regeln, Webhook bei dauerhaften Fehlern, Tests

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Fable 5.1 committed 2026-09-28 10:19:05 +00:00
1 parent 7df0b72019
commit 887e1e54a0
10 files changed
+409 -107

No files matched your search

+41 -20
View File
@@ -1,11 +1,12 @@
// testApp.js zuerst: es prueft beim Laden, dass useTestEnv() gelaufen ist, bevor config.js die Umgebung liest
// testApp.js zuerst: es prueft beim Laden, dass useTestEnv() vor config.js gelaufen ist
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,
* Feste Testdaten für die Routen-Tests (Aufgaben 7–13): Kunden c1–c4, 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).
* (Neuabschluss-Tests), c4 ist ein Testkonto (ABO_TEST_EMAILS=test@example.invalid im Test setzen).
* Erst nach useTestEnv() dynamisch importieren (siehe testApp.js).
*/
const DAY_MS = 86400000
@@ -15,18 +16,20 @@ 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 },
c4: { $id: 'c4', appwriteUserId: 'u4', email: 'test@example.invalid', name: 'Testkonto', status: 'active', portalAccessEnabled: true },
}
/** Abo-Dokument mit id als Dokument-ID und Stripe-Abo-ID (in Produktion immer gleich). */
export const subscriptionDoc = (id, overrides = {}) => ({ ...overrides, $id: id, stripeSubscriptionId: id })
// 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',
export const hostingDoc = subscriptionDoc('sub_h', {
customerId: 'c1',
email: 'k@example.invalid',
stripeCustomerId: 'cus_c1',
stripeSubscriptionId: 'sub_h',
status: 'active',
productKey: 'hosting',
planKey: config.products.hosting.lookupKey,
@@ -49,27 +52,23 @@ export const hostingDoc = {
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 = {
/** Wartungs-Abo sub_w von c1 (im Test meist per subscriptionDoc('sub_h', { ...wartungDoc }) eingespielt). */
export const wartungDoc = subscriptionDoc('sub_w', {
...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 = {
export const hosting2Doc = subscriptionDoc('sub_h2', {
...hostingDoc,
$id: 'sub_h2',
customerId: 'c2',
email: 'k2@example.invalid',
stripeCustomerId: 'cus_c2',
stripeSubscriptionId: 'sub_h2',
}
})
const consentAt = new Date().toISOString()
@@ -90,7 +89,21 @@ export const customerCookie = (customerId = 'c1') =>
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. */
/** Admin im Modus "Als Kunde ansehen" (portalAdmin.js /view-as): Bestellen und Aendern sind gesperrt. */
export const viewAsCookie = (customerId = 'c1') =>
sessionCookie({
role: 'admin',
viewAs: true,
isAdmin: true,
appwriteUserId: 'u_admin',
adminName: 'Kenso',
adminEmail: 'kenso@webklar.com',
customerId,
name: customers[customerId]?.name || '',
email: customers[customerId]?.email || '',
})
/** Kunden c1–c4 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)
@@ -101,10 +114,18 @@ 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, ...).
* dazu ein Zusatzspeicher-Posten, wenn storageUnits > 0. Die Standard-ID ist 28 Zeichen lang wie
* bei Stripe (sub_ + 24); fuer den Spiegel von sub_h explizit id: 'sub_h' uebergeben. 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 } = {}) {
export function stripeSubscription({
id = 'sub_1SD6vPRxAbCdEfGhIjKlMnOp',
customerId = 'c1',
productKey = 'hosting',
storageUnits = 0,
metadata = {},
...rest
} = {}) {
const product = config.products[productKey]
const items = [
{