"Gültig bis" ist nur die Frist zum Annehmen. Jetzt steht auch das Rabattende da: - Formular "Neues Angebot", Kunde ohne Abo: Schätzung "Rabatt läuft N Monate ab Abschluss und endet voraussichtlich zwischen <heute + N> und <gültig bis + N>", mit Hinweis auf den späteren Start nach der Widerrufsfrist (gut 2 Wochen) - Formular, laufendes Abo: genaues Ende aus den nächsten Abbuchungen (Anker = Leistungsbeginn, Monatsende wie bei Stripe), auch in der Erfolgsmeldung - Angebotsliste: offen -> Schätzung, aktiv -> "Rabatt endet am ..." aus dem Abo-Dokument (bzw. "endete", "endet mit dem Abo am ...", "unbekannt (Abo nicht gefunden)") Rechnung in server/services/offerDiscount.js: Der Coupon deckt die N Abbuchungen ab dem Anwenden. Beim Checkout ist das der Abo-Start, bei laufendem Abo und Testkonto (Coupon per Webhook) der Zeitpunkt des Anhängens; Testkonten laufen deshalb einen Monat länger. Tests: npm test (37 Fälle, davon 8 neu). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
5.0 KiB
JavaScript
82 lines
5.0 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
|
|
// Vor dem Import von config.js setzen (jede Testdatei laeuft in einem eigenen Prozess)
|
|
process.env.ABO_TEST_EMAILS = 'test@example.invalid'
|
|
const { chargesFrom, redeemedDiscount } = await import('../server/services/offerDiscount.js')
|
|
const { adminOfferView } = await import('../server/services/offers.js')
|
|
|
|
test('Abbuchungen: vom Anker aus, Monatsende wie bei Stripe', () => {
|
|
assert.deepEqual(chargesFrom('2026-09-26T08:00:00.000Z', '2026-09-26T08:00:00.000Z', 3), [
|
|
'2026-09-26T08:00:00.000Z', '2026-10-26T08:00:00.000Z', '2026-11-26T08:00:00.000Z',
|
|
])
|
|
// 31. bleibt Anker: Februar kuerzer, danach wieder der 31.
|
|
assert.deepEqual(chargesFrom('2026-01-31T10:00:00.000Z', '2026-01-31T10:00:00.000Z', 4), [
|
|
'2026-01-31T10:00:00.000Z', '2026-02-28T10:00:00.000Z', '2026-03-31T10:00:00.000Z', '2026-04-30T10:00:00.000Z',
|
|
])
|
|
// Ab einem Zeitpunkt mitten im Abrechnungsmonat: naechste Abbuchung
|
|
assert.deepEqual(chargesFrom('2026-01-15T10:00:00.000Z', '2026-03-20T12:00:00.000Z', 2), [
|
|
'2026-04-15T10:00:00.000Z', '2026-05-15T10:00:00.000Z',
|
|
])
|
|
assert.deepEqual(chargesFrom(null, '2026-03-20T12:00:00.000Z', 2), [])
|
|
})
|
|
|
|
test('Angebot beim Abschluss, Leistung sofort: Abbuchungen 1..N rabattiert', () => {
|
|
const sub = { startedAt: '2026-09-26T08:00:00.000Z', trialEnd: null }
|
|
const d = redeemedDiscount({ months: 2, appliedAt: '2026-09-26T08:00:05.000Z', attachedLater: false }, sub)
|
|
assert.deepEqual(d, { known: true, firstChargeAt: '2026-09-26T08:00:00.000Z', endsAt: '2026-11-26T08:00:00.000Z', subscriptionEndsAt: null })
|
|
})
|
|
|
|
test('Angebot beim Abschluss, Beginn nach Widerrufsfrist: Rabatt ab Leistungsbeginn', () => {
|
|
const sub = { startedAt: '2026-09-26T08:00:00.000Z', trialEnd: '2026-10-13T04:00:00.000Z' }
|
|
const d = redeemedDiscount({ months: 2, appliedAt: '2026-09-26T08:00:05.000Z', attachedLater: false }, sub)
|
|
assert.equal(d.firstChargeAt, '2026-10-13T04:00:00.000Z')
|
|
assert.equal(d.endsAt, '2026-12-13T04:00:00.000Z')
|
|
})
|
|
|
|
test('Testkonto (Coupon nach der ersten Abbuchung): Abbuchungen 2..N+1', () => {
|
|
const sub = { startedAt: '2026-09-26T08:00:00.000Z', trialEnd: null }
|
|
const d = redeemedDiscount({ months: 2, appliedAt: '2026-09-26T08:00:05.000Z', attachedLater: true }, sub)
|
|
assert.equal(d.firstChargeAt, '2026-10-26T08:00:00.000Z')
|
|
assert.equal(d.endsAt, '2026-12-26T08:00:00.000Z')
|
|
})
|
|
|
|
test('Laufendes Abo: ab der naechsten Abbuchung nach dem Anhaengen', () => {
|
|
const sub = { startedAt: '2026-03-14T09:00:00.000Z', trialEnd: null }
|
|
const d = redeemedDiscount({ months: 2, appliedAt: '2026-09-26T20:00:00.000Z', attachedLater: true }, sub)
|
|
assert.equal(d.firstChargeAt, '2026-10-14T09:00:00.000Z')
|
|
assert.equal(d.endsAt, '2026-12-14T09:00:00.000Z')
|
|
})
|
|
|
|
test('Abo endet vor dem Rabatt: Abo-Ende wird gemeldet, sonst nicht', () => {
|
|
const base = { startedAt: '2026-09-26T08:00:00.000Z', trialEnd: null }
|
|
const offer = { months: 3, appliedAt: '2026-09-26T08:00:05.000Z', attachedLater: false }
|
|
assert.equal(redeemedDiscount(offer, { ...base, cancelAt: '2026-10-26T08:00:00.000Z' }).subscriptionEndsAt, '2026-10-26T08:00:00.000Z')
|
|
assert.equal(redeemedDiscount(offer, { ...base, endedAt: '2026-10-01T08:00:00.000Z' }).subscriptionEndsAt, '2026-10-01T08:00:00.000Z')
|
|
assert.equal(redeemedDiscount(offer, { ...base, cancelAt: '2027-09-26T08:00:00.000Z' }).subscriptionEndsAt, null)
|
|
})
|
|
|
|
test('Ohne Abo-Dokument ist das Rabattende unbekannt', () => {
|
|
assert.deepEqual(redeemedDiscount({ months: 2, appliedAt: '2026-09-26T08:00:05.000Z', attachedLater: false }, null), { known: false })
|
|
assert.deepEqual(redeemedDiscount({ months: 2, appliedAt: null, attachedLater: true }, { startedAt: '2026-09-26T08:00:00.000Z' }), { known: false })
|
|
})
|
|
|
|
test('Admin-Ansicht: Rabatt nur bei eingeloesten Angeboten, Testkonto erkannt', () => {
|
|
const doc = {
|
|
$id: 'offer-1', $createdAt: '2026-09-26T07:00:00.000Z', status: 'aktiv', scope: 'neu', months: 2,
|
|
monthlyPriceCents: 2500, regularPriceCents: 5000, validUntil: '2026-10-27T00:00:00.000Z',
|
|
appliedAt: '2026-09-26T08:00:05.000Z', stripeSubscriptionId: 'sub_1', customerId: 'c1',
|
|
}
|
|
const sub = { $id: 'sub_1', startedAt: '2026-09-26T08:00:00.000Z', trialEnd: null }
|
|
const normal = adminOfferView(doc, { name: 'Kunde', email: 'kunde@example.invalid' }, sub)
|
|
assert.equal(normal.testAccount, false)
|
|
assert.equal(normal.discount.endsAt, '2026-11-26T08:00:00.000Z')
|
|
const testView = adminOfferView(doc, { name: 'Test', email: 'TEST@example.invalid' }, sub)
|
|
assert.equal(testView.testAccount, true)
|
|
assert.equal(testView.discount.endsAt, '2026-12-26T08:00:00.000Z')
|
|
const running = adminOfferView({ ...doc, scope: 'laufend', appliedAt: '2026-10-01T12:00:00.000Z' }, { email: 'kunde@example.invalid' }, sub)
|
|
assert.equal(running.discount.firstChargeAt, '2026-10-26T08:00:00.000Z')
|
|
assert.equal(adminOfferView({ ...doc, status: 'offen', appliedAt: null }, { email: 'kunde@example.invalid' }, null).discount, null)
|
|
assert.deepEqual(adminOfferView(doc, { email: 'kunde@example.invalid' }, undefined).discount, { known: false })
|
|
})
|