Abo-Angebote: Admin sieht, bis wann der Rabatt läuft
"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>
This commit is contained in:
1 parent
c600118374
commit
054b04d5b6
5 files changed
+227
-12
No files matched your search
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto'
|
||||
import { config } from '../config.js'
|
||||
import { createDocument, deleteDocument, getDocument, listDocuments, Query, updateDocument } from './appwriteAdmin.js'
|
||||
import { sendAdminNotice, sendCustomerMail } from './mailer.js'
|
||||
import { chargesFrom, redeemedDiscount } from './offerDiscount.js'
|
||||
import { ACTIVE_STATUSES, getCurrentSubscription, getStripe, isStripeConfigured } from './stripeBilling.js'
|
||||
import { shortRef } from './takeoverCredits.js'
|
||||
|
||||
@@ -101,9 +102,19 @@ function stripeDashboardUrl(kind, id) {
|
||||
return `https://dashboard.stripe.com/${test}${kind}/${id}`
|
||||
}
|
||||
|
||||
export function adminOfferView(doc, customer) {
|
||||
/**
|
||||
* sub = Abo-Dokument des eingeloesten Angebots (doc.stripeSubscriptionId), fuer das Rabattende.
|
||||
* Testkonten bekommen den Coupon erst nach dem Abschluss (activateOfferFromCheckout), wie ein laufendes Abo.
|
||||
*/
|
||||
export function adminOfferView(doc, customer, sub = null) {
|
||||
const testAccount = isTestEmail(customer?.email)
|
||||
const attachedLater = doc.scope === 'laufend' || testAccount
|
||||
return {
|
||||
...offerView(doc),
|
||||
testAccount,
|
||||
discount: doc.status === OFFER_STATUS.active
|
||||
? redeemedDiscount({ months: doc.months, appliedAt: doc.appliedAt, attachedLater }, sub)
|
||||
: null,
|
||||
customerId: doc.customerId,
|
||||
customerName: customer?.name || customer?.companyName || '(Kunde nicht gefunden)',
|
||||
customerCode: customer?.code || '',
|
||||
@@ -155,7 +166,15 @@ export async function offerContext(customerId) {
|
||||
testAccount: isTestEmail(customer.email),
|
||||
},
|
||||
subscription: sub
|
||||
? { status: sub.status, active: ACTIVE_STATUSES.has(sub.status), currentPeriodEnd: sub.currentPeriodEnd || null }
|
||||
? {
|
||||
status: sub.status,
|
||||
active: ACTIVE_STATUSES.has(sub.status),
|
||||
currentPeriodEnd: sub.currentPeriodEnd || null,
|
||||
// Laufendes Abo: [0] = erste rabattierte Abbuchung, [N] = Rabattende bei N Angebotsmonaten
|
||||
upcomingCharges: ACTIVE_STATUSES.has(sub.status)
|
||||
? chargesFrom(sub.trialEnd || sub.startedAt, new Date(), config.offers.maxMonths + 1)
|
||||
: [],
|
||||
}
|
||||
: null,
|
||||
openOffer: open ? offerView(open) : null,
|
||||
regularPriceCents: config.stripe.priceAmountCents,
|
||||
@@ -333,7 +352,7 @@ export async function createOffer(body, admin) {
|
||||
if (mail.sent) doc = await updateDocument(COLLECTION(), id, { notifiedAt: new Date().toISOString() })
|
||||
}
|
||||
}
|
||||
return { doc, customer, mail }
|
||||
return { doc, customer, mail, sub: running ? sub : null }
|
||||
}
|
||||
|
||||
export async function withdrawOffer(id, admin) {
|
||||
|
||||
Reference in new issue
Block a user