Öffnungszeiten: nach Abo-Ende für 30 € behalten
Neuer Modus neben Bestellen und Einschalten (Spec 3.3): POST /api/funktionen/zeiten/behalten startet einen Checkout mit zweck 'behalten', ohne den Zustand zu ändern; der Webhook-Zweig bucht vor markPaid/adoptPaidSession unter feature-admin zum Kauf (Frist weg, stripeSessionId = Sitzung), schaltet eine abgeschaltete Funktion wieder ein (Neubau nach der Sperre), ohne neues Ticket, mit K15 im Behalten-Wortlaut. Passt die Sitzung nicht, geht T19 nach der Sperre raus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
97e6971276
commit
53253f0451
6 files changed
+1116
-12
No files matched your search
@@ -324,7 +324,7 @@ function offenerFall(doc) {
|
||||
}
|
||||
|
||||
/** Neuester Eintrag zuerst, hoechstens 300 Zeichen (wie die Notiz im Ticketsystem). */
|
||||
function notizVorn(previous, entry) {
|
||||
export function notizVorn(previous, entry) {
|
||||
const neu = entry.slice(0, NOTE_MAX)
|
||||
const aelter = String(previous || '')
|
||||
return aelter ? `${neu} | ${aelter}`.slice(0, NOTE_MAX) : neu
|
||||
@@ -757,7 +757,7 @@ export async function customerOf(customerId) {
|
||||
}
|
||||
|
||||
/** Link zur Stripe-Rechnung; fehlt sie noch oder ist sie nicht lesbar, geht die Mail ohne Link raus. */
|
||||
async function invoiceOf(invoiceRef) {
|
||||
export async function invoiceOf(invoiceRef) {
|
||||
const id = typeof invoiceRef === 'string' ? invoiceRef : invoiceRef?.id
|
||||
if (!id) return { invoiceId: '', invoiceUrl: '' }
|
||||
try {
|
||||
@@ -818,7 +818,7 @@ async function teamNotice(doc, customer, { failed = false, keinAbo = false } = {
|
||||
}
|
||||
|
||||
/** Bezahlte Sitzung ohne passende Bestellung: das Team muss von Hand klaeren (T16, Block ungeklaert). */
|
||||
async function unmatchedNotice(session) {
|
||||
export async function unmatchedNotice(session) {
|
||||
const { customerId, projectId, featureKey } = session.metadata || {}
|
||||
const customer = customerId ? await customerOf(customerId).catch(() => null) : null
|
||||
await sendT16(
|
||||
@@ -920,14 +920,18 @@ export function meldeZahlungFall(doc, fall, { customer = null } = {}) {
|
||||
* am (frisch gelesenen) Dokument der offene Fall 'abo_ende' steht - so auch, wenn der Abgleich vor dem Webhook-Versand zuerst meldet;
|
||||
* ticket_offen, wenn das Dokument ein Ticket hat (kein Ticket und keine spaete Zahlung: keiner von beiden); rechnung mit Rechnungslink.
|
||||
* Genau einmal je Dokument (siehe meldeEinmal), wirft nie.
|
||||
* ticketOffen und rechnungUrl ueberschreiben, was sonst am Dokument steht (Ticket vorhanden, invoiceUrl): bei einer Zahlung, die nicht zu
|
||||
* dessen Ticket und Rechnung gehoert (Behalten, zeiten/behalten.js), sagt T19 sonst "Ticket offen" und zeigt die Rechnung einer anderen Zahlung.
|
||||
* rechnungUrl '' heisst "keine Rechnung"; null (Vorgabe) nimmt die des Dokuments.
|
||||
* @param {object} doc portalFeatures-Dokument
|
||||
* @param {{ customer?: object|null, sitzung?: string, nachAboEnde?: boolean }} [options]
|
||||
* @param {{ customer?: object|null, sitzung?: string, nachAboEnde?: boolean, ticketOffen?: boolean|null, rechnungUrl?: string|null }} [options]
|
||||
* @returns {Promise<{ sent: boolean, schonGemeldet?: boolean }>}
|
||||
*/
|
||||
export async function meldeErstattung(doc, { customer = null, sitzung = doc.stripeSessionId, nachAboEnde = false } = {}) {
|
||||
export async function meldeErstattung(doc, { customer = null, sitzung = doc.stripeSessionId, nachAboEnde = false, ticketOffen = null, rechnungUrl = null } = {}) {
|
||||
return meldeEinmal(doc, 'T19 Erstattung faellig', async (frisch) => {
|
||||
const kopf = await meldeKopf(frisch, customer)
|
||||
const woid = frisch.ticketId ? await ticketWoidOf(frisch).catch(() => frisch.ticketId) : ''
|
||||
const rechnung = rechnungUrl ?? frisch.invoiceUrl ?? ''
|
||||
return sendAdminNotice({
|
||||
template: 'T19',
|
||||
values: {
|
||||
@@ -936,12 +940,12 @@ export async function meldeErstattung(doc, { customer = null, sitzung = doc.stri
|
||||
betrag: featureConfig(frisch.featureKey)?.priceLabel || '',
|
||||
sitzung: sitzung || '',
|
||||
woid,
|
||||
rechnung_url: frisch.invoiceUrl || '',
|
||||
rechnung_url: rechnung,
|
||||
},
|
||||
blocks: {
|
||||
zahlung_nach_ende: nachAboEnde || frisch.zahlungMeldung === 'abo_ende',
|
||||
ticket_offen: Boolean(frisch.ticketId),
|
||||
rechnung: Boolean(frisch.invoiceUrl),
|
||||
ticket_offen: ticketOffen ?? Boolean(frisch.ticketId),
|
||||
rechnung: Boolean(rechnung),
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -968,7 +972,18 @@ async function meldeFallZurZahlung(doc, fall, session) {
|
||||
if (!ergebnis.sent && !ergebnis.schonGemeldet) throw new Error(`Meldung zur Zahlung ${session.id} (${fall}) nicht gesendet`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Zahlung "fuer 30 EUR behalten" (Oeffnungszeiten live nach dem Abo-Ende, Spec 3.3): eigener Weg in zeiten/behalten.js, nie ueber
|
||||
* markPaid und adoptPaidSession (die schalten nur aus 'ordered' ein und meldeten die Zahlung einer laufenden Funktion als ungeklaert).
|
||||
* Dynamischer Import: zeiten/* importiert dieses Modul.
|
||||
*/
|
||||
async function onBehaltenPaid(session) {
|
||||
const { bucheBehalten } = await import('./zeiten/behalten.js')
|
||||
await bucheBehalten(session)
|
||||
}
|
||||
|
||||
async function onFeaturePaid(session) {
|
||||
if (session.metadata?.zweck === 'behalten') return onBehaltenPaid(session)
|
||||
const { invoiceId, invoiceUrl } = await invoiceOf(session.invoice)
|
||||
const paid = { sessionId: session.id, invoiceId, invoiceUrl }
|
||||
let { doc, created, fall } = await markPaid(paid)
|
||||
@@ -1027,16 +1042,24 @@ export async function handleFeatureCheckoutEvent(event) {
|
||||
await onFeaturePaid(session)
|
||||
return
|
||||
case 'checkout.session.async_payment_failed': {
|
||||
if (session.metadata?.zweck === 'behalten') return verwerfeBehaltenSitzung(session)
|
||||
const doc = await markCheckoutFailed(session.id)
|
||||
if (doc && doc.state === 'off') await teamNotice(doc, await customerOf(doc.customerId), { failed: true })
|
||||
return
|
||||
}
|
||||
case 'checkout.session.expired':
|
||||
if (session.metadata?.zweck === 'behalten') return verwerfeBehaltenSitzung(session)
|
||||
await markCheckoutFailed(session.id)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/** Behalten-Zahlung abgelaufen oder fehlgeschlagen: die offene Sitzung am Dokument wird frei (Dynamischer Import wie onBehaltenPaid). */
|
||||
async function verwerfeBehaltenSitzung(session) {
|
||||
const { verwerfeBehalten } = await import('./zeiten/behalten.js')
|
||||
await verwerfeBehalten(session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Selbstheilung der Oeffnungszeiten (Spec 3.2): fuer jedes Dokument der Zeiten in 'setup' holt holeZeitenNach fehlende Schritte
|
||||
* nach (Neubau, Einbau-Ticket, K24; es sperrt je Dokument selbst und fasst Kauf, aktiv und aus nicht an). Hierher und nicht in
|
||||
|
||||
Reference in new issue
Block a user