Funktionen: Bestellung serialisiert, Sitzung nachtragen, Freischaltung einmalig
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0cc35bf1a5
commit
8374a570aa
3 files changed
+120
-34
No files matched your search
@@ -265,11 +265,17 @@ export async function activateClosedTickets({ now = new Date(), customerId = nul
|
||||
throw err
|
||||
}
|
||||
if (ticket.status !== 'Closed') continue
|
||||
const updated = await updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
...stateFields('active'),
|
||||
activatedAt: now.toISOString(),
|
||||
// Je Dokument gesperrt und neu gelesen: GET /api/funktionen, ein zweiter Tab und der 5-Minuten-Job
|
||||
// duerfen dieselbe Funktion nicht beide freischalten (und beide benachrichtigen)
|
||||
const updated = await withLocks([`feature-activate:${doc.$id}`], async () => {
|
||||
const fresh = await getDocument(config.collections.portalFeatures, doc.$id)
|
||||
if (fresh.state !== 'setup') return null
|
||||
return updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
...stateFields('active'),
|
||||
activatedAt: now.toISOString(),
|
||||
})
|
||||
})
|
||||
activated.push(updated)
|
||||
if (updated) activated.push(updated)
|
||||
}
|
||||
return activated
|
||||
}
|
||||
@@ -353,25 +359,63 @@ async function teamNotice(doc, customer, { failed = false } = {}) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Bezahlte Sitzung, die kein Dokument ueber die Sitzungs-ID findet (zwei parallele Bestellungen haben die
|
||||
* ID ueberschrieben): das Dokument aus den Metadaten nehmen, wenn es noch 'ordered' oder 'off' ist, und die
|
||||
* bezahlte Sitzung eintragen. Sonst laut loggen - der Kunde hat gezahlt und nichts wird eingerichtet.
|
||||
*/
|
||||
async function adoptPaidSession(session) {
|
||||
const { customerId, projectId, featureKey } = session.metadata || {}
|
||||
const doc = customerId && projectId && featureKey ? await getFeatureDoc(customerId, projectId, featureKey) : null
|
||||
if (!doc || (doc.state !== 'ordered' && doc.state !== 'off')) {
|
||||
console.error(
|
||||
`[funktionen] BEZAHLTE Sitzung ${session.id} ohne passende Bestellung (Kunde ${customerId}, Projekt ${projectId}, Zustand ${doc?.state || 'kein Dokument'}) - bitte von Hand pruefen`
|
||||
)
|
||||
return false
|
||||
}
|
||||
await updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
...stateFields('ordered'),
|
||||
stripeSessionId: session.id,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
async function onFeaturePaid(session) {
|
||||
const { invoiceId, invoiceUrl } = await invoiceOf(session.invoice)
|
||||
const { doc, created } = await markPaid({ sessionId: session.id, invoiceId, invoiceUrl })
|
||||
const paid = { sessionId: session.id, invoiceId, invoiceUrl }
|
||||
let { doc, created } = await markPaid(paid)
|
||||
if (!doc && (await adoptPaidSession(session))) ({ doc, created } = await markPaid(paid))
|
||||
if (!created) return
|
||||
const customer = await customerOf(doc.customerId)
|
||||
// Ab hier ist die Bestellung gebucht; ein Wiederholungsversuch von Stripe sieht created:false und
|
||||
// wuerde keine Mail mehr schicken. Darum scheitert jede Mail einzeln und der Webhook antwortet trotzdem 200.
|
||||
let customer = null
|
||||
try {
|
||||
customer = await customerOf(doc.customerId)
|
||||
} catch (err) {
|
||||
console.error('[funktionen] Kunde fuer K15/T16 nicht lesbar:', err.message)
|
||||
}
|
||||
if (customer?.email) {
|
||||
await sendCustomerMail(customer.email, {
|
||||
template: 'K15',
|
||||
values: {
|
||||
kunde_name: customer.name || '',
|
||||
projekt: await projectName(doc.projectId),
|
||||
preis: eur(config.newsSetup.amountCents),
|
||||
zeit: config.funktionen.setupEstimate,
|
||||
rechnung_url: invoiceUrl,
|
||||
},
|
||||
blocks: { rechnung: Boolean(invoiceUrl) },
|
||||
}).catch((err) => console.error('[funktionen] K15 fehlgeschlagen:', err.message))
|
||||
try {
|
||||
await sendCustomerMail(customer.email, {
|
||||
template: 'K15',
|
||||
values: {
|
||||
kunde_name: customer.name || '',
|
||||
projekt: await projectName(doc.projectId),
|
||||
preis: eur(config.newsSetup.amountCents),
|
||||
zeit: config.funktionen.setupEstimate,
|
||||
rechnung_url: invoiceUrl,
|
||||
},
|
||||
blocks: { rechnung: Boolean(invoiceUrl) },
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('[funktionen] K15 fehlgeschlagen:', err.message)
|
||||
}
|
||||
}
|
||||
try {
|
||||
await teamNotice(doc, customer)
|
||||
} catch (err) {
|
||||
console.error('[funktionen] T16 fehlgeschlagen:', err.message)
|
||||
}
|
||||
await teamNotice(doc, customer)
|
||||
}
|
||||
|
||||
/** Stripe-Ereignis eines Funktions-Checkouts (isFeatureCheckoutEvent); idempotent, Fehler -> Stripe wiederholt. */
|
||||
|
||||
Reference in new issue
Block a user