Stripe: Testrabatt-Namen, Produktbild, Hosting-Leistungen, Tests für Bestandskunden
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c4607e9c43
commit
7df0b72019
7 files changed
+297
-52
No files matched your search
@@ -43,7 +43,9 @@ export function productView(key) {
|
||||
amountCents: product.amountCents,
|
||||
minTermMonths: product.minTermMonths,
|
||||
minutesPerMonth: product.minutesPerMonth,
|
||||
features: product.features,
|
||||
// Kopie, kein Verweis auf den Katalog: aufrufender Code darf das zurueckgegebene Array
|
||||
// veraendern, ohne den geteilten Katalogeintrag fuer alle anderen zu verfaelschen (B10)
|
||||
features: product.features.map((row) => [...row]),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,9 @@ export async function ensurePrice(key) {
|
||||
name: catalog.name,
|
||||
...(catalog.description ? { description: catalog.description } : {}),
|
||||
...(catalog.statementDescriptor ? { statement_descriptor: catalog.statementDescriptor } : {}),
|
||||
// Produktbild: eigenes Katalogbild, sonst das generische Logo (Fix round 1, Important 2 - war
|
||||
// beim Verallgemeinern von ensureAboPrice zu ensurePrice verloren gegangen)
|
||||
images: [catalog.image || config.stripe.productImage],
|
||||
metadata: { webklar: PRODUCT_METADATA[key] },
|
||||
})
|
||||
const price = await stripe.prices.create({
|
||||
@@ -100,6 +103,11 @@ export async function ensurePrice(key) {
|
||||
export const PORTAL_HEADLINE = 'WEBklar – Abo verwalten'
|
||||
|
||||
let cachedPortalConfigId = null
|
||||
/** Nur fuer Tests: Cache zuruecksetzen, damit beide Zweige (Update noetig / schon richtig) einzeln pruefbar sind. */
|
||||
export function resetPortalConfigCacheForTests() {
|
||||
cachedPortalConfigId = null
|
||||
}
|
||||
|
||||
/** Kundenportal-Konfiguration (Zahlungsmittel, Rechnungen, Kuendigung zum Periodenende). */
|
||||
export async function ensurePortalConfiguration() {
|
||||
if (cachedPortalConfigId) return cachedPortalConfigId
|
||||
@@ -107,16 +115,25 @@ export async function ensurePortalConfiguration() {
|
||||
const { data } = await stripe.billingPortal.configurations.list({ active: true, limit: 20 })
|
||||
const own = data.find((c) => c.metadata?.webklar === 'wartungs-abo')
|
||||
if (own) {
|
||||
cachedPortalConfigId = own.id
|
||||
// Aeltere Konfiguration (frueher nur Wartung) traegt noch die alte Ueberschrift -> anpassen
|
||||
// Aeltere Konfiguration (frueher nur Wartung) traegt noch die alte Ueberschrift -> anpassen.
|
||||
// Fix round 1, Minor 6: Cache erst nach Erfolg setzen, damit ein Fehlschlag beim naechsten
|
||||
// Aufruf (z. B. Klick auf "Abo verwalten") erneut versucht wird statt fuer immer stehen zu
|
||||
// bleiben; ein Fehlschlag bricht den Aufruf selbst nicht ab (die Portal-Sitzung soll trotzdem
|
||||
// zustande kommen), nur eine Warnung im Log.
|
||||
if (own.business_profile?.headline !== PORTAL_HEADLINE) {
|
||||
await stripe.billingPortal.configurations.update(own.id, {
|
||||
business_profile: {
|
||||
headline: PORTAL_HEADLINE,
|
||||
terms_of_service_url: `${config.publicBaseUrl}/abo-bedingungen.html`,
|
||||
},
|
||||
})
|
||||
try {
|
||||
await stripe.billingPortal.configurations.update(own.id, {
|
||||
business_profile: {
|
||||
headline: PORTAL_HEADLINE,
|
||||
terms_of_service_url: `${config.publicBaseUrl}/abo-bedingungen.html`,
|
||||
},
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn(`[stripe] Kundenportal-Konfiguration ${own.id}: Überschrift konnte nicht aktualisiert werden - ${err.message}`)
|
||||
return own.id
|
||||
}
|
||||
}
|
||||
cachedPortalConfigId = own.id
|
||||
return own.id
|
||||
}
|
||||
const created = await stripe.billingPortal.configurations.create({
|
||||
@@ -299,7 +316,13 @@ export function minTermMonthsFor(termsVersion) {
|
||||
*/
|
||||
export function termStartOf(sub) {
|
||||
const termStartAt = sub.metadata?.termStartAt
|
||||
if (termStartAt) return new Date(termStartAt)
|
||||
if (termStartAt) {
|
||||
const date = new Date(termStartAt)
|
||||
if (!Number.isNaN(date.getTime())) return date
|
||||
// Von Hand verstellte/beschaedigte Metadaten duerfen syncSubscription (toISOString) und
|
||||
// cancelSubscriptionAt nicht mit einer Exception abschiessen - Warnung, Fallback wie ohne Wechsel
|
||||
console.warn(`[stripe] Abo ${sub.id || '?'}: ungültiges termStartAt in den Metadaten ("${termStartAt}") - falle auf Leistungsbeginn zurück`)
|
||||
}
|
||||
return new Date(((sub.trial_end || sub.start_date) || 0) * 1000)
|
||||
}
|
||||
|
||||
@@ -405,12 +428,17 @@ async function getOrCreateStripeCustomer(customer) {
|
||||
return created.id
|
||||
}
|
||||
|
||||
// Testrabatt-IDs je Produkt: amount_off = amountCents - 50 -> es werden nur 0,50 € abgebucht
|
||||
const TEST_COUPON_IDS = { wartung: config.abo.testCouponId, hosting: 'WEBKLAR-TEST-050-HOSTING' }
|
||||
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1)
|
||||
|
||||
/** Rabatt fuer Testkonten holen oder (einmalig) anlegen. productKey: 'wartung' | 'hosting'. */
|
||||
/**
|
||||
* Rabatt fuer Testkonten holen oder (einmalig) anlegen. productKey: 'wartung' | 'hosting'; die ID
|
||||
* kommt aus dem Katalog (products.<key>.testCouponId), wie amountCents und der lookup_key.
|
||||
* Fix round 1, Important 1: Stripe begrenzt den Coupon-Namen auf 40 Zeichen - der volle Produktname
|
||||
* ("Testzugang WEBklar (0,50 €) – WEBklar Hosting-Abo" = 49) hätte in echtem Stripe 400 zurückgegeben.
|
||||
*/
|
||||
export async function ensureTestCoupon(productKey) {
|
||||
const id = TEST_COUPON_IDS[productKey]
|
||||
const catalog = getProduct(productKey) // 'mailStorage' oder unbekannt -> 400 (kein Testrabatt dafuer)
|
||||
const id = catalog.testCouponId
|
||||
if (!id) {
|
||||
const err = new Error(`Kein Testrabatt fuer ${productKey}`)
|
||||
err.status = 400
|
||||
@@ -419,14 +447,13 @@ export async function ensureTestCoupon(productKey) {
|
||||
const stripe = getStripe()
|
||||
const existing = await stripe.coupons.retrieve(id).catch(() => null)
|
||||
if (existing) return existing.id
|
||||
const catalog = getProduct(productKey)
|
||||
const created = await stripe.coupons.create({
|
||||
id,
|
||||
name: `Testzugang WEBklar (0,50 €) – ${catalog.name}`,
|
||||
name: `Testzugang ${capitalize(productKey)} (0,50 €)`,
|
||||
amount_off: catalog.amountCents - 50,
|
||||
currency: 'eur',
|
||||
duration: 'forever',
|
||||
metadata: { webklar: `${productKey}-test` },
|
||||
metadata: { webklar: `${PRODUCT_METADATA[productKey]}-test` },
|
||||
})
|
||||
return created.id
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user