Funktionen: Bestellschutz, serialisiertes markPaid, idx_session
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4b1df8fbfa
commit
770cbe32b7
3 files changed
+53
-19
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
import { config } from '../config.js'
|
||||
import { createDocument, getDocument, listDocuments, updateDocument, Query } from './appwriteAdmin.js'
|
||||
import { withLocks } from './keyedLock.js'
|
||||
import { createFeatureTicket } from './ticketAdmin.js'
|
||||
|
||||
export const FEATURE_KEYS = ['news']
|
||||
@@ -117,6 +118,7 @@ export async function listCustomerFeatures(customerId) {
|
||||
const projects = await listDocuments(config.collections.websiteProjects, [
|
||||
Query.equal('customerId', customerId),
|
||||
Query.orderDesc('$createdAt'),
|
||||
Query.limit(1000),
|
||||
])
|
||||
const docs = await listDocuments(config.collections.portalFeatures, [
|
||||
Query.equal('customerId', customerId),
|
||||
@@ -142,6 +144,9 @@ export async function listCustomerFeatures(customerId) {
|
||||
export async function markOrdered({ customerId, projectId, key, sessionId, consent = {} }) {
|
||||
assertKey(key)
|
||||
const doc = await ensureFeatureDoc(customerId, projectId, key)
|
||||
if (doc.state === 'setup' || doc.state === 'active') {
|
||||
throw httpError(409, 'Diese Funktion ist bereits bestellt oder freigeschaltet')
|
||||
}
|
||||
return updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
featureKey: key,
|
||||
...stateFields('ordered'),
|
||||
@@ -164,28 +169,35 @@ async function findBySession(sessionId) {
|
||||
return doc || null
|
||||
}
|
||||
|
||||
/** Idempotent: Ticket und Wechsel nach 'setup' nur beim Uebergang aus 'ordered'. */
|
||||
/**
|
||||
* Idempotent und je Session serialisiert: nur der Uebergang aus 'ordered' legt das Ticket an. Erst
|
||||
* wird auf 'setup' gestellt, dann das Ticket erstellt; scheitert das, bleibt 'setup' ohne ticketId
|
||||
* (das Portal zeigt "Ticket fehlt") und der Fehler wird weitergereicht.
|
||||
*/
|
||||
export async function markPaid({ sessionId, invoiceId, invoiceUrl }) {
|
||||
const doc = await findBySession(sessionId)
|
||||
if (!doc) return { doc: null, created: false }
|
||||
if (doc.state !== 'ordered') return { doc, created: false }
|
||||
if (!sessionId) return { doc: null, created: false }
|
||||
return withLocks([`feature-paid:${sessionId}`], async () => {
|
||||
const doc = await findBySession(sessionId)
|
||||
if (!doc) return { doc: null, created: false }
|
||||
if (doc.state !== 'ordered') return { doc, created: false }
|
||||
|
||||
const project = await getDocument(config.collections.websiteProjects, doc.projectId)
|
||||
const { ticketId } = await createFeatureTicket({
|
||||
customerId: doc.customerId,
|
||||
project,
|
||||
featureKey: doc.featureKey,
|
||||
siteKey: doc.siteKey,
|
||||
const setup = await updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
...stateFields('setup'),
|
||||
purchaseStatus: 'paid',
|
||||
unlockedByPurchase: true,
|
||||
stripeInvoiceId: invoiceId || '',
|
||||
invoiceUrl: invoiceUrl || '',
|
||||
})
|
||||
const project = await getDocument(config.collections.websiteProjects, doc.projectId)
|
||||
const { ticketId } = await createFeatureTicket({
|
||||
customerId: doc.customerId,
|
||||
project,
|
||||
featureKey: doc.featureKey,
|
||||
siteKey: doc.siteKey,
|
||||
})
|
||||
const updated = await updateDocument(config.collections.portalFeatures, setup.$id, { ticketId })
|
||||
return { doc: updated, created: true }
|
||||
})
|
||||
const updated = await updateDocument(config.collections.portalFeatures, doc.$id, {
|
||||
...stateFields('setup'),
|
||||
purchaseStatus: 'paid',
|
||||
unlockedByPurchase: true,
|
||||
stripeInvoiceId: invoiceId || '',
|
||||
invoiceUrl: invoiceUrl || '',
|
||||
ticketId,
|
||||
})
|
||||
return { doc: updated, created: true }
|
||||
}
|
||||
|
||||
/** Zahlung abgebrochen oder fehlgeschlagen: nur eine noch offene Bestellung faellt auf 'off' zurueck. */
|
||||
|
||||
Reference in new issue
Block a user