import { test, after } from 'node:test' import assert from 'node:assert/strict' import { startAppwriteMock } from './support/appwriteMock.js' import { startFakeStripe } from './support/fakeStripe.js' import { useTestEnv } from './support/testEnv.js' import { schemaKeys } from './support/schema.js' // createFeatureCheckout (Stripe-Checkout je Funktion aus dem Register config.features), direkter Aufruf gegen den Stripe-Nachbau const aw = await startAppwriteMock({ schema: { subscriptions: schemaKeys('subscriptions') } }) const st = await startFakeStripe() after(() => Promise.all([aw.close(), st.close()])) useTestEnv({ appwriteUrl: aw.url, stripeUrl: st.url }) const { config } = await import('../server/config.js') const { createFeatureCheckout } = await import('../server/services/stripeBilling.js') const customer = { $id: 'c1', name: 'Test Kunde', email: 'kunde@example.invalid', code: 'K-1' } const CONSENT_AT = '2026-10-04T10:00:00.000Z' const sessionCalls = () => st.calls.filter((c) => c.method === 'POST' && c.path === '/v1/checkout/sessions') const lastCall = () => sessionCalls().at(-1) const priceOf = (call) => st.prices.get(call.body['line_items[0][price]']) test('News: Preis 3000, URLs und Rechnungsdaten wie bisher', async () => { const created = await createFeatureCheckout(customer, { projectId: 'p1', key: 'news', consentAt: CONSENT_AT }) assert.match(created.id, /^cs_test_/) assert.equal(created.url, `https://checkout.stripe.test/c/pay/${created.id}`) const call = lastCall() assert.equal(priceOf(call).unit_amount, 3000) assert.equal(call.body['mode'], 'payment') assert.equal(call.body['client_reference_id'], 'c1') assert.equal(call.body['success_url'], `${config.publicBaseUrl}/dashboard.html#news?bestellt=1`) assert.equal(call.body['cancel_url'], `${config.publicBaseUrl}/dashboard.html#news`) assert.equal(call.body['invoice_creation[enabled]'], 'true') assert.equal(call.body['invoice_creation[invoice_data][description]'], 'News-Pinnwand – Einrichtung') assert.equal(call.body['invoice_creation[invoice_data][footer]'], config.stripe.invoiceFooter) assert.equal(call.body['invoice_creation[invoice_data][metadata][featureKey]'], 'news') assert.equal(call.body['invoice_creation[invoice_data][metadata][projectId]'], 'p1') // Metadaten der Sitzung unveraendert assert.equal(call.body['metadata[customerId]'], 'c1') assert.equal(call.body['metadata[projectId]'], 'p1') assert.equal(call.body['metadata[featureKey]'], 'news') assert.equal(call.body['metadata[consentAt]'], CONSENT_AT) // Ablauf in hoechstens 24 Stunden, Karte + SEPA const now = Math.floor(Date.now() / 1000) assert.ok(Number(call.body['expires_at']) > now) assert.ok(Number(call.body['expires_at']) < now + 24 * 60 * 60) assert.equal(call.body['payment_method_types[0]'], 'card') assert.equal(call.body['payment_method_types[1]'], 'sepa_debit') }) test('News: ohne consentAt steht kein consentAt in den Metadaten', async () => { await createFeatureCheckout(customer, { projectId: 'p1' }) const call = lastCall() assert.equal(call.body['metadata[featureKey]'], 'news', 'Standardschluessel news') assert.ok(!('metadata[consentAt]' in call.body)) }) test('News: SEPA nicht verfuegbar -> zweiter Versuch nur mit Karte', async () => { const before = sessionCalls().length st.failNext({ method: 'POST', path: '/v1/checkout/sessions', status: 400, message: 'The payment method type "sepa_debit" is invalid.' }) const created = await createFeatureCheckout(customer, { projectId: 'p1', key: 'news', consentAt: CONSENT_AT }) const calls = sessionCalls().slice(before) assert.equal(calls.length, 2) assert.equal(calls[0].body['payment_method_types[1]'], 'sepa_debit') assert.equal(calls[1].body['payment_method_types[0]'], 'card') assert.ok(!('payment_method_types[1]' in calls[1].body), 'nur Karte') assert.ok(created.id && created.url) }) test('Termine: Preis 14900, Rechnungsdaten mit featureKey und projectId, URLs mit Projekt', async () => { const created = await createFeatureCheckout(customer, { projectId: 'p/1', key: 'termine', consentAt: CONSENT_AT }) assert.equal(created.url, `https://checkout.stripe.test/c/pay/${created.id}`) const call = lastCall() assert.equal(priceOf(call).unit_amount, 14900) assert.equal(priceOf(call).lookup_key, 'webklar_termine_einrichtung_14900') assert.equal(call.body['invoice_creation[invoice_data][description]'], 'Terminplaner – Einrichtung') assert.equal(call.body['invoice_creation[invoice_data][footer]'], config.stripe.invoiceFooter) assert.equal(call.body['invoice_creation[invoice_data][metadata][featureKey]'], 'termine') assert.equal(call.body['invoice_creation[invoice_data][metadata][projectId]'], 'p/1') assert.equal(call.body['success_url'], `${config.publicBaseUrl}/dashboard.html#termine?bestellt=1&projekt=p%2F1`) assert.equal(call.body['cancel_url'], `${config.publicBaseUrl}/dashboard.html#termine?projekt=p%2F1`) assert.equal(call.body['metadata[customerId]'], 'c1') assert.equal(call.body['metadata[projectId]'], 'p/1') assert.equal(call.body['metadata[featureKey]'], 'termine') assert.equal(call.body['metadata[consentAt]'], CONSENT_AT) const now = Math.floor(Date.now() / 1000) assert.ok(Number(call.body['expires_at']) > now) assert.ok(Number(call.body['expires_at']) < now + 24 * 60 * 60) assert.equal(call.body['payment_method_types[1]'], 'sepa_debit') const sessionObj = st.checkoutSessions.get(created.id) assert.equal(sessionObj.metadata.featureKey, 'termine') }) test('Termine: SEPA nicht verfuegbar -> zweiter Versuch nur mit Karte, gleiche URLs', async () => { const before = sessionCalls().length st.failNext({ method: 'POST', path: '/v1/checkout/sessions', status: 400, message: 'The payment method type "sepa_debit" is invalid.' }) await createFeatureCheckout(customer, { projectId: 'p2', key: 'termine', consentAt: CONSENT_AT }) const calls = sessionCalls().slice(before) assert.equal(calls.length, 2) assert.ok(!('payment_method_types[1]' in calls[1].body), 'nur Karte') assert.equal(calls[1].body['success_url'], `${config.publicBaseUrl}/dashboard.html#termine?bestellt=1&projekt=p2`) assert.equal(calls[1].body['cancel_url'], `${config.publicBaseUrl}/dashboard.html#termine?projekt=p2`) }) test('unbekannter Schluessel: Fehler 400 vor jedem Stripe-Aufruf', async () => { const before = st.calls.length for (const key of ['analyse', '__proto__', 'toString', '']) { await assert.rejects( createFeatureCheckout(customer, { projectId: 'p1', key, consentAt: CONSENT_AT }), (err) => err.status === 400 ) } assert.equal(st.calls.length, before, 'kein einziger Stripe-Aufruf') })