Stripe: Katalog Terminplaner-Einrichtung (149 €) und Checkout je Funktion aus dem Register
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c584d220c8
commit
98fa03aad7
6 files changed
+238
-13
No files matched your search
@@ -0,0 +1,113 @@
|
||||
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')
|
||||
})
|
||||
Reference in new issue
Block a user