Zahlung nach Abo-Ende: Zusatztests Webhook und Abgleich (T19 nie doppelt)
Nur Tests in test/termineAboAbgleich.test.js, kein Produktionscode: handleFeatureCheckoutEvent und gleicheAboAb gegeneinander (Review Focus 1, nie Geld ohne Spur, nie doppelt). - Zahlung nach dem Abschalten: genau ein T19 (zahlung_nach_ende, Sitzung), kein Ticket, kein K15/T16; Wiederholung und async_payment_succeeded stumm - T19-Versand scheitert: Webhook wirft, Zahlung gebucht, Fall offen; erste Wiederholung mit funktionierendem Versand sendet genau ein T19 - Folgetag-Abgleich: K22 vor_einrichtung, Marker aboEndeMailAt, kein zweites T19 - Abgleich vor der Webhook-Wiederholung und parallel (Promise.all): insgesamt ein T19 - Grund team: T16 abgeschaltet, kein T19; Abo laeuft (ordered/setup): Ticket, K15, T16 - Rennen Zahlung vor Abgleich: Ticket, T16 und ein T19; Stripe-Retry stumm Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c5009da570
commit
d66dd60a76
1 file changed
+341
-1
@@ -5,7 +5,7 @@ import { mailSink } from './support/mailSink.js'
|
|||||||
import { schemaKeys } from './support/schema.js'
|
import { schemaKeys } from './support/schema.js'
|
||||||
import { useTestEnv } from './support/testEnv.js'
|
import { useTestEnv } from './support/testEnv.js'
|
||||||
|
|
||||||
// Abo-Abgleich des Terminplaners (server/services/termine/aboAbgleich.js, Terminplaner TP2 E4/E5/E6): aktive Terminplaner ohne laufendes
|
// Abo-Abgleich des Terminplaners (server/services/termine/aboAbgleich.js, Terminplaner TP2 E4/E5/E6; E10: Zusammenspiel mit dem Zahlungs-Webhook, letzter Abschnitt): aktive Terminplaner ohne laufendes
|
||||||
// Wartungs-Abo abschalten (Schritt 1), Einrichtungen, die das Abo-Ende erreicht, abschalten und erstatten (Schritt 1b, E5), die
|
// Wartungs-Abo abschalten (Schritt 1), Einrichtungen, die das Abo-Ende erreicht, abschalten und erstatten (Schritt 1b, E5), die
|
||||||
// Mail K22 nachholen (Schritt 2) und bei zurueckgekehrtem Abo wieder einschalten, mit K23 (Schritt 3, E6). Aufbau wie termineJobs.test.js: Nachbau von Appwrite mit Schema und Standard-Limit 25, Mails in
|
// Mail K22 nachholen (Schritt 2) und bei zurueckgekehrtem Abo wieder einschalten, mit K23 (Schritt 3, E6). Aufbau wie termineJobs.test.js: Nachbau von Appwrite mit Schema und Standard-Limit 25, Mails in
|
||||||
// der Senke, feste Zeit JETZT. Stripe wird nie erreicht (Port 1).
|
// der Senke, feste Zeit JETZT. Stripe wird nie erreicht (Port 1).
|
||||||
@@ -1887,3 +1887,343 @@ test('Sofortpfad: wartet auf die Sperre feature-admin des Projekts; T19 und K22
|
|||||||
assert.equal(frei.length, 3, 'T19 und zwei K22')
|
assert.equal(frei.length, 3, 'T19 und zwei K22')
|
||||||
assert.ok(frei.every(([, ...rest]) => rest.every(Boolean)), JSON.stringify(frei))
|
assert.ok(frei.every(([, ...rest]) => rest.every(Boolean)), JSON.stringify(frei))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- Zahlung nach dem Abo-Ende: Webhook und Abgleich zusammen (E10)
|
||||||
|
//
|
||||||
|
// Review Focus 1: eine Zahlung, die nach einem Zustandswechsel eintrifft, hinterlaesst immer eine Spur (Ticket ODER T16/T19 an das
|
||||||
|
// Team) und nie ein Doppel. Das Verhalten selbst steckt in siteFeatures.js (markPaid-Fall, meldeErstattung, onFeaturePaid, B10);
|
||||||
|
// diese Tests lassen Webhook (handleFeatureCheckoutEvent) und Abgleich (gleicheAboAb) gegeneinander laufen.
|
||||||
|
|
||||||
|
const SITZUNG = 'cs_test_1'
|
||||||
|
const event = (type, session) => ({ id: `evt_${Math.random()}`, object: 'event', type, data: { object: session } })
|
||||||
|
const paidSession = (id = SITZUNG, projectId = 'p_projekt01', extra = {}) => ({
|
||||||
|
id,
|
||||||
|
object: 'checkout.session',
|
||||||
|
mode: 'payment',
|
||||||
|
payment_status: 'paid',
|
||||||
|
metadata: { customerId: KUNDE, projectId, featureKey: 'termine' },
|
||||||
|
...extra,
|
||||||
|
})
|
||||||
|
const COMPLETED = 'checkout.session.completed'
|
||||||
|
const ASYNC_BEZAHLT = 'checkout.session.async_payment_succeeded'
|
||||||
|
/** Das Stripe-Ereignis einer bezahlten Sitzung (noch einmal mit demselben Ereignis aufrufen = Wiederholung durch Stripe). */
|
||||||
|
const zahlungsEreignis = (typ = COMPLETED, session = paidSession()) => event(typ, session)
|
||||||
|
|
||||||
|
const T16_BETREFF = 'Neue Funktion bestellt: Terminplaner'
|
||||||
|
const K15_BETREFF = 'Deine Bestellung: Terminplaner'
|
||||||
|
const t16 = () => mails().filter((m) => m.subject === T16_BETREFF)
|
||||||
|
const k15 = () => mails().filter((m) => m.subject === K15_BETREFF)
|
||||||
|
const betreffs = () => mails().map((m) => m.subject)
|
||||||
|
const tickets = () => aw.docs('workorders').size
|
||||||
|
/** Das Anlegen eines Tickets schreibt eine Zeile ins Log (ticketAdmin); die Tests mit Ticket halten die Ausgabe sauber. Zurueck setzt afterEach (restoreAll). */
|
||||||
|
const stilleTicketLog = () => mock.method(console, 'log', () => {})
|
||||||
|
const TEAM = () => [...config.chatNotify.emails].sort()
|
||||||
|
const an = (mail) => mail.to.map((a) => a.address).sort()
|
||||||
|
|
||||||
|
/** Unbezahlte Bestellung, deren Wartungs-Abo schon beendet ist: der Abgleich (ohne Abo) hat sie abgeschaltet, die Zahlung steht noch aus. */
|
||||||
|
async function bestellungNachAboEnde() {
|
||||||
|
legeBestellung(1)
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1 }))
|
||||||
|
assert.equal(funktion().state, 'disabled')
|
||||||
|
assert.equal(funktion().disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.equal(funktion().unlockedByPurchase, false)
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
test('E10: Zahlung nach dem Abschalten - genau ein T19 (zahlung_nach_ende, Sitzung), kein Ticket, kein K15/T16, bezahlt gebucht, abgeschaltet; Wiederholung und async_payment_succeeded melden nichts mehr', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
const ticketsVorher = tickets()
|
||||||
|
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
|
||||||
|
assert.equal(t19().length, 1, 'genau ein T19')
|
||||||
|
const mailT19 = t19()[0]
|
||||||
|
assert.deepEqual(an(mailT19), TEAM(), 'T19 geht an das Team, nicht an den Kunden')
|
||||||
|
assert.match(mailT19.text, /Zahlung nach dem Abo-Ende/, 'Block zahlung_nach_ende')
|
||||||
|
assert.match(mailT19.text, /cs_test_1/, 'die Sitzungs-ID steht drin')
|
||||||
|
assert.match(mailT19.text, /Silke Müller/)
|
||||||
|
assert.match(mailT19.text, /Praxis Muster/)
|
||||||
|
assert.doesNotMatch(mailT19.text, /Ticket noch offen/, 'es gibt kein Ticket')
|
||||||
|
assert.deepEqual(betreffs(), [T19_BETREFF], 'sonst keine Mail: weder K15 noch T16 noch K22')
|
||||||
|
assert.equal(k15().length + t16().length, 0)
|
||||||
|
assert.equal(tickets(), ticketsVorher, 'kein Eintrag in workorders')
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
|
||||||
|
let doc = funktion()
|
||||||
|
assert.equal(doc.unlockedByPurchase, true)
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(doc.state, 'disabled', 'der Zustand bleibt abgeschaltet')
|
||||||
|
assert.equal(doc.enabled, false)
|
||||||
|
assert.equal(doc.disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.ok(!doc.ticketId, 'kein Ticket am Dokument')
|
||||||
|
assert.equal(doc.stripeSessionId, SITZUNG)
|
||||||
|
assert.match(doc.adminNote, /Zahlung eingegangen \(Sitzung cs_test_1\), nichts eingerichtet/, 'die Spur steht in der Notiz')
|
||||||
|
|
||||||
|
// dasselbe Ereignis noch einmal (Stripe-Wiederholung) und das SEPA-Folgeereignis: stumm, nichts geschrieben
|
||||||
|
const gebucht = { ...doc }
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(t19().length, 1, 'nie ein zweites T19')
|
||||||
|
assert.deepEqual(betreffs(), [T19_BETREFF])
|
||||||
|
assert.deepEqual(funktion(), gebucht, 'nichts neu verbucht')
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: T19-Versand scheitert - der Webhook wirft (500), die Zahlung ist gebucht, die Meldung offen; jede Wiederholung wirft weiter, die erste mit funktionierendem Versand sendet genau ein T19', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
t19Scheitert()
|
||||||
|
|
||||||
|
await assert.rejects(handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED)), /Meldung zur Zahlung cs_test_1 \(abo_ende\) nicht gesendet/)
|
||||||
|
let doc = funktion()
|
||||||
|
assert.equal(doc.unlockedByPurchase, true, 'die Zahlung ist trotzdem gebucht')
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(doc.zahlungMeldung, 'abo_ende', 'der Fall bleibt offen (nicht gemeldet)')
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
const gebucht = { ...doc }
|
||||||
|
|
||||||
|
// Stripe wiederholt, der Versand geht weiter nicht: wieder 500, nichts neu verbucht
|
||||||
|
await assert.rejects(handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED)), /nicht gesendet/)
|
||||||
|
await assert.rejects(handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT)), /nicht gesendet/)
|
||||||
|
assert.deepEqual(funktion(), gebucht)
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
|
||||||
|
// der Versand geht wieder: genau ein T19, danach ist die Wiederholung stumm
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.match(t19()[0].text, /Zahlung nach dem Abo-Ende/)
|
||||||
|
assert.match(t19()[0].text, /cs_test_1/)
|
||||||
|
doc = funktion()
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.deepEqual(betreffs(), [T19_BETREFF])
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: Folgetag-Abgleich nach der Zahlung - K22 vor_einrichtung an den Kunden, aboEndeMailAt gesetzt, kein zweites T19 (Marker gemeldet), danach nichts mehr', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.ok(!funktion().aboEndeMailAt, 'K22 gehoert dem Abgleich')
|
||||||
|
|
||||||
|
const tag2 = plus(JETZT, TAG)
|
||||||
|
assert.deepEqual(await laufe(tag2), ergebnis({ geprueft: 1, mails: 1 }), 'mails zaehlt K22, nicht T19')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
const mailK22 = k22()[0]
|
||||||
|
assert.deepEqual(an(mailK22), ['silke@example.com'], 'K22 geht an den Kunden')
|
||||||
|
assert.equal(mailK22.subject, 'Dein Terminplaner wurde beendet – Praxis Muster')
|
||||||
|
assert.match(mailK22.text, /noch vor der Einrichtung Deines Terminplaners/, 'Block vor_einrichtung')
|
||||||
|
assert.match(mailK22.text, /erstatten Dir die Einrichtung \(149 €\) vollständig/)
|
||||||
|
assert.match(mailK22.text, /am 05\.10\.2026 beendet/, 'Enddatum aus disabledAt')
|
||||||
|
assert.doesNotMatch(mailK22.text, /Was jetzt gilt|löschen wir/, 'es gibt nichts zu loeschen')
|
||||||
|
assert.equal(t19().length, 1, 'kein zweites T19')
|
||||||
|
assert.deepEqual(betreffs().map((s) => s === T19_BETREFF), [true, false], 'zuerst T19 (Webhook), dann K22 (Abgleich)')
|
||||||
|
const doc = funktion()
|
||||||
|
assert.equal(ms(doc.aboEndeMailAt), tag2.getTime())
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
assert.equal(doc.disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
|
||||||
|
// Tag 3 und eine spaete Wiederholung des Ereignisses: nichts mehr
|
||||||
|
const vorher = { ...doc }
|
||||||
|
assert.deepEqual(await laufe(plus(tag2, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: Abgleich VOR der Wiederholung des Webhooks - Schritt 2b meldet T19 (die erste Verbuchung hat bezahlt), K22 folgt, die Wiederholung bleibt stumm', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
t19Scheitert()
|
||||||
|
await assert.rejects(handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED)), /nicht gesendet/)
|
||||||
|
assert.equal(funktion().unlockedByPurchase, true)
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'abo_ende')
|
||||||
|
assert.equal(mails().length, 0)
|
||||||
|
|
||||||
|
// der Versand geht wieder, der Tagesabgleich kommt vor Stripes Wiederholung
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
const tag2 = plus(JETZT, TAG)
|
||||||
|
assert.deepEqual(await laufe(tag2), ergebnis({ geprueft: 1, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 1, 'der Abgleich meldet T19')
|
||||||
|
assert.match(t19()[0].text, /Zahlung nach dem Abo-Ende/, 'als Zahlung nach dem Abo-Ende (der offene Fall steht am Dokument)')
|
||||||
|
assert.match(t19()[0].text, /cs_test_1/)
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.deepEqual(betreffs().map((s) => s === T19_BETREFF), [true, false], 'T19 vor K22')
|
||||||
|
const doc = funktion()
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(ms(doc.aboEndeMailAt), tag2.getTime())
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
|
||||||
|
// jetzt kommt die Wiederholung des Webhooks: kein Wurf, kein zweites T19
|
||||||
|
const vorher = { ...doc }
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(t19().length, 1, 'nie doppelt')
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: Abgleich und Wiederholung des Webhooks gleichzeitig (Promise.all) - insgesamt ein T19, ein K22, kein Wurf', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
t19Scheitert()
|
||||||
|
await assert.rejects(handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED)), /nicht gesendet/)
|
||||||
|
setMailTransportForTests(sink)
|
||||||
|
|
||||||
|
const tag2 = plus(JETZT, TAG)
|
||||||
|
const [erg] = await Promise.all([laufe(tag2), handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))])
|
||||||
|
assert.deepEqual(erg, ergebnis({ geprueft: 1, mails: 1 }), 'der Abgleich sendet K22, T19 hoechstens einmal, gleich wer zuerst kommt')
|
||||||
|
assert.equal(t19().length, 1, 'insgesamt ein T19')
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
const doc = funktion()
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(ms(doc.aboEndeMailAt), tag2.getTime())
|
||||||
|
assert.deepEqual(await laufe(plus(tag2, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: erste Zahlung und Tagesabgleich gleichzeitig (Promise.all) - gleich in welcher Reihenfolge: ein T19, nach dem naechsten Lauf ein K22, nie ein Doppel', async () => {
|
||||||
|
await bestellungNachAboEnde()
|
||||||
|
|
||||||
|
const tag2 = plus(JETZT, TAG)
|
||||||
|
await Promise.all([laufe(tag2), handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))])
|
||||||
|
assert.equal(t19().length, 1, 'genau ein T19 (Webhook oder Abgleich, nie beide)')
|
||||||
|
assert.equal(funktion().purchaseStatus, 'paid')
|
||||||
|
assert.equal(funktion().unlockedByPurchase, true)
|
||||||
|
assert.equal(funktion().zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
|
||||||
|
await laufe(plus(tag2, TAG))
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED)) // Stripe-Wiederholung
|
||||||
|
await laufe(plus(tag2, 2 * TAG))
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(k22().length, 1, 'K22 genau einmal')
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
assert.equal(k15().length + t16().length, 0)
|
||||||
|
assert.equal(funktion().state, 'disabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('E10: Zahlung auf ein vom Team abgeschaltetes Dokument (Grund team) - T16 "abgeschaltet" wie in B10b, kein T19, kein Ticket, der Abgleich fasst es nie an', async () => {
|
||||||
|
legeBestellung(1)
|
||||||
|
await disable(F1, { by: 'Kenso', reason: 'team' })
|
||||||
|
assert.equal(funktion().disabledReason, 'team')
|
||||||
|
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(COMPLETED))
|
||||||
|
assert.equal(t16().length, 1, 'genau ein T16')
|
||||||
|
assert.deepEqual(an(t16()[0]), TEAM())
|
||||||
|
assert.match(t16()[0].text, /Bezahlt, aber schon vorhanden/, 'Block bereits_aktiv')
|
||||||
|
assert.match(t16()[0].text, /abgeschaltet/, 'Zustand abgeschaltet')
|
||||||
|
assert.match(t16()[0].text, /cs_test_1/)
|
||||||
|
assert.equal(t19().length, 0, 'kein T19: das Abo-Ende ist nicht der Grund')
|
||||||
|
assert.equal(k15().length, 0)
|
||||||
|
assert.deepEqual(betreffs(), [T16_BETREFF])
|
||||||
|
assert.equal(tickets(), 0)
|
||||||
|
const doc = funktion()
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
assert.equal(doc.disabledReason, 'team')
|
||||||
|
assert.equal(doc.unlockedByPurchase, true)
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
|
||||||
|
// der Abgleich (ohne Abo) laesst ein Team-Dokument in Ruhe: keine Abo-Abfrage, keine Mail; die Wiederholung ist stumm
|
||||||
|
aw.resetListCalls()
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis())
|
||||||
|
assert.equal(abfragenAufAbos(), 0)
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.deepEqual(betreffs(), [T16_BETREFF])
|
||||||
|
assert.deepEqual(funktion(), doc)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const [name, legeDokument] of [
|
||||||
|
['ordered', () => legeBestellung(1)],
|
||||||
|
['setup ohne Ticket (Ticket fehlt, Stripe wiederholt)', () => legeFunktion(1, { state: 'setup', enabled: false, stripeSessionId: SITZUNG })],
|
||||||
|
]) {
|
||||||
|
test(`E10: Zahlung bei laufendem Abo (${name}) - unveraendert: Ticket, K15 an den Kunden, T16 ohne kein_abo, kein T19; die Wiederholung legt nichts doppelt an`, async () => {
|
||||||
|
stilleTicketLog()
|
||||||
|
wartung('active')
|
||||||
|
legeDokument()
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1 }), 'der Abgleich fasst eine Bestellung bei laufendem Abo nicht an')
|
||||||
|
assert.equal(funktion().state, name === 'ordered' ? 'ordered' : 'setup')
|
||||||
|
|
||||||
|
const ereignis = zahlungsEreignis(COMPLETED)
|
||||||
|
await handleFeatureCheckoutEvent(ereignis)
|
||||||
|
assert.equal(tickets(), 1, 'ein Einrichtungs-Ticket')
|
||||||
|
const doc = funktion()
|
||||||
|
assert.equal(doc.state, 'setup')
|
||||||
|
assert.equal(doc.unlockedByPurchase, true)
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.ok(aw.docs('workorders').has(doc.ticketId), 'das Ticket haengt am Dokument')
|
||||||
|
assert.ok(!doc.zahlungMeldung, 'kein Zahlungsfall')
|
||||||
|
|
||||||
|
assert.equal(k15().length, 1)
|
||||||
|
assert.deepEqual(an(k15()[0]), ['silke@example.com'])
|
||||||
|
assert.equal(t16().length, 1)
|
||||||
|
assert.deepEqual(an(t16()[0]), TEAM())
|
||||||
|
assert.doesNotMatch(t16()[0].text, /Kein laufendes Wartungs-Abo/, 'das Abo laeuft')
|
||||||
|
assert.doesNotMatch(t16()[0].text, /Bezahlt, aber schon vorhanden/)
|
||||||
|
assert.match(t16()[0].text, new RegExp(`Ticket: ${aw.docs('workorders').get(doc.ticketId).woid}\\b`), 'T16 nennt das Ticket')
|
||||||
|
assert.equal(t19().length, 0)
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
|
||||||
|
// Stripe wiederholt: kein zweites Ticket, keine weitere Mail; der Abgleich bleibt dabei ruhig
|
||||||
|
await handleFeatureCheckoutEvent(ereignis)
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(tickets(), 1)
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(mails().length, 2)
|
||||||
|
assert.equal(funktion().state, 'setup')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('E10: Rennen - Zahlung VOR dem Abgleich: Ticket, K15 und T16 (mit kein_abo), dann der Abgleich: T19 und K22; Stripe-Retry desselben Ereignisses ist stumm', async () => {
|
||||||
|
stilleTicketLog()
|
||||||
|
legeBestellung(1) // das Wartungs-Abo ist beendet (kein Abo-Dokument), die Bestellung wartet noch auf die Zahlung
|
||||||
|
const ereignis = zahlungsEreignis(COMPLETED)
|
||||||
|
|
||||||
|
await handleFeatureCheckoutEvent(ereignis)
|
||||||
|
assert.equal(funktion().state, 'setup', 'gebucht und im Aufbau')
|
||||||
|
assert.equal(tickets(), 1, 'Ticket angelegt')
|
||||||
|
assert.equal(k15().length, 1)
|
||||||
|
assert.equal(t16().length, 1)
|
||||||
|
assert.match(t16()[0].text, /Kein laufendes Wartungs-Abo/, 'T16 sagt dem Team, dass das Abo fehlt')
|
||||||
|
assert.equal(t19().length, 0, 'noch kein T19: das Dokument ist nicht abgeschaltet')
|
||||||
|
const ticketId = funktion().ticketId
|
||||||
|
assert.ok(ticketId)
|
||||||
|
|
||||||
|
// der Abgleich kommt danach: bezahlter Aufbau ohne Abo => abgeschaltet, T19 (ohne "nach dem Abo-Ende"), K22 vor_einrichtung
|
||||||
|
assert.deepEqual(await laufe(JETZT), ergebnis({ geprueft: 1, abgeschaltet: 1, mails: 1 }))
|
||||||
|
assert.equal(t19().length, 1, 'T19 hoechstens einmal')
|
||||||
|
assert.doesNotMatch(t19()[0].text, /Zahlung nach dem Abo-Ende/, 'die Zahlung kam vor dem Abo-Ende')
|
||||||
|
assert.match(t19()[0].text, /Ticket noch offen/)
|
||||||
|
assert.equal(k22().length, 1)
|
||||||
|
assert.equal(tickets(), 1, 'kein zweites Ticket')
|
||||||
|
assert.equal(aw.docs('workorders').get(ticketId).status, 'Open', 'das Ticket bleibt offen, das Team erstattet')
|
||||||
|
let doc = funktion()
|
||||||
|
assert.equal(doc.state, 'disabled')
|
||||||
|
assert.equal(doc.disabledReason, 'abo_ende_vor_einrichtung')
|
||||||
|
assert.equal(doc.zahlungMeldung, 'gemeldet')
|
||||||
|
assert.equal(doc.purchaseStatus, 'paid')
|
||||||
|
assert.equal(doc.ticketId, ticketId)
|
||||||
|
assert.equal(mails().length, 4, 'K15, T16, T19, K22')
|
||||||
|
|
||||||
|
// Stripe wiederholt dasselbe Ereignis (und das SEPA-Folgeereignis): stumm, nichts geschrieben, kein zweites Ticket
|
||||||
|
const vorher = { ...doc }
|
||||||
|
await handleFeatureCheckoutEvent(ereignis)
|
||||||
|
await handleFeatureCheckoutEvent(zahlungsEreignis(ASYNC_BEZAHLT))
|
||||||
|
assert.equal(mails().length, 4)
|
||||||
|
assert.equal(t19().length, 1)
|
||||||
|
assert.equal(tickets(), 1)
|
||||||
|
assert.deepEqual(funktion(), vorher)
|
||||||
|
assert.deepEqual(await laufe(plus(JETZT, TAG)), ergebnis({ geprueft: 1 }))
|
||||||
|
assert.equal(mails().length, 4)
|
||||||
|
})
|
||||||
Reference in new issue
Block a user