5 Commits (5967c56d1682a4eac8bb616986a9e272608a978c)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
42fd059fdd |
Office aus dem Verkauf nehmen, abschaltbar statt gelöscht
tests / pest (push) Has been cancelled
Details
tests / assets (push) Has been cancelled
Details
tests / release (push) Has been cancelled
Details
Office (ONLYOFFICE über einen künftigen gemeinsamen Dokumentenserver) wurde an drei Stellen beworben, ohne dass irgendwo im Repository ein Dokumentenserver existiert: der Kachel "Office im Browser" auf der Preistafel, dem Paketmerkmal `office` in Team und Business, und dem Zusatzmodul `collabora_pro` für 22,80 €/Monat. Alle drei sind jetzt stillgelegt, aber nicht gelöscht — ein einziger dokumentierter Schalter (LandingController::OFFICE_ON_SALE) plus eine neu veröffentlichte Planversion holen das Versprechen zurück, sobald der Dokumentenserver steht. Das Paketmerkmal wird über das im Katalog bereits etablierte Handover-Muster entfernt (neue Migration, analog zu switch_to_new_plan_ladder): die laufende Version von Team/Business wird geschlossen und durch eine identische ohne `office` ersetzt. Bestehende Verträge bleiben auf ihrer alten, eingefrorenen Version stehen und behalten das Merkmal unverändert. Die dritte Planversion für Team/Business hat 24 Bestandstests berührt, die eine feste Versionsnummer oder eine feste Preis-/Versionszahl annahmen — repariert, überwiegend durch dynamisches Lesen der aktuellen Version statt eines eingetippten Werts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
d4f534fbbb |
Interne Pakete gehören nicht nach Stripe
stripe:sync-catalogue überspringt jetzt jede Familie mit internal = true — weder Produkt noch Preis, im Trockenlauf wie im echten Lauf, und zwar bevor irgendetwas über sie gelesen oder geschrieben wird. Ein Stripe-Preis lässt sich nicht löschen, nur archivieren, und ein Paket, das nie über Stripe abgerechnet wird, gehört deshalb nicht ins Konto. Eine Familie, die bereits Stripe-IDs trägt (verkäuflich war, jetzt intern ist, wie Enterprise), bleibt unangetastet: nichts wird gelöscht oder ersetzt, es kommt nur nichts Neues mehr hinzu. BillingChecks::billing.catalogue_synced bekam dieselbe Ausnahme — sonst wäre die Bereitschaftsseite durch genau diese Änderung dauerhaft rot geworden, weil das interne Testpaket und Enterprise veröffentlicht und sales_enabled sind, ihre Preise aber nie synchronisiert werden. AddonPrices, SyncStripeAddonItems, stripe:reprice-subscriptions und stripe:sweep-orphan-prices wurden geprüft: alle vier sind bereits sicher, weil ein verschenkter Vertrag nie ein stripe_subscription_id trägt und die anderen beiden Befehle nur über vorhandene Stripe-Objekte laufen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
f961205512 |
Die neue Leiter: Start 39, Team 79, Business 139
Zugeschnitten auf die Maschine, die dasteht. 388 GB vergebbar geteilt durch 40 GB Platte sind neun Startkunden statt drei — Speicher und RAM gehen damit gleichzeitig aus, wo vorher sechs RAM-Plaetze verfielen. 351 Euro Umsatz je Server statt 147. Umgeschaltet wird durch Beenden und Nachfolgen, nicht durch Aendern: eine veroeffentlichte Version ist unveraenderlich, und laufende Vertraege tragen ihren eigenen Schnappschuss. Als Migration und nicht als Befehl, weil der Katalog von einer Migration gesetzt wird — sonst blieben Neuinstallation und Testsuite auf einer Leiter, die wir nicht mehr verkaufen. Enterprise verlaesst den Verkauf (2000 GB finden auf 388 GB keinen Host), das Testpaket heisst Intern und wird intern — es steht ausserdem zum ersten Mal im Katalog einer frischen Installation, statt von Hand angelegt werden zu muessen. Dreissig Testdateien nennen die neuen Zahlen. Drei Faelle waren keine Zahlen: Vertraege auf Enterprise entstehen nur noch als Bestandsvertraege (Helfer asGrandfathered() in tests/Pest.php), die Preisblatt-Stufe "Premium" haengt am Paket, das den Laden verlassen hat, und ein Katalogleser, der die einzige Preiszeile einer Familie suchte, findet seit der Handreichung zwei. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
fed4acf31c |
Accept terms instead of a start date, and fix the sender no server accepts
**The test mail was rejected: "553 5.7.1 Sender address rejected: not owned by user no-reply@…".** Every purpose mailbox here has its own SMTP account, and a mail server lets an account send only from the address it owns. Mail::to() hands back a pending mail bound to the DEFAULT mailer and sendNow() then ignores the mailer the mailable asked for, so a mail addressed support@ went out over the no-reply@ login. It sends through the mailable's own mailer now, and the cloud-ready preview — the one mailable with no mailbox at all — takes the provisioning mailbox like the real notification does. Writing the test for that found the same bug in production code: **InvoiceMail and OrderConfirmationMail set their From to billing@ and never named a mailer**, so both went out over mail.default's no-reply@ login. On this installation no customer had ever received an invoice mail or an order confirmation; they rendered perfectly, queued without complaint and were refused at the door. MailSenderOwnershipTest now scans for the mismatch: a mail that takes a mailbox From must use that mailbox's mailer. **The box on the order page accepts the terms now**, not an immediate start. It used to carry the whole FAGG §16 sentence, which read like a choice between "now" and "in fourteen days" — and there is no second option. The terms are what regulate the sale, so they had to exist: resources/views/legal/terms.blade.php replaces the placeholder with fourteen sections written from what this software actually does — the delivery, the capacity queue, the full refund on withdrawal, the cancellation at period end, the deletion deadlines. The company data comes from CompanyProfile, so the page and the invoices cannot drift. No availability figure and no liability cap has been invented. No order goes through without it: the button is unusable until the box is ticked and says why, and CheckoutController still refuses server-side — the browser half refuses nothing. The request field is `terms_accepted`; the Stripe metadata key stays `immediate_start`, because a session opened before a deploy is paid after it and the webhook would find nothing under a new name. **"Wird der Account nach fünf Tagen gelöscht?"** Only an unconfirmed one. That was the whole of what we said, so the answer looked like yes. The second rule now exists and is stated: PruneDormantAccounts removes a confirmed account after a year when it never had a package — no customer record at all, which is where every order, contract and seven-year invoice hangs. A fortnight's notice goes out first, once, and `dormant_warned_at` is what permits the deletion: an account whose warning never went out is never removed. Signing in resets the clock, measured off the device rows because users has no last_login_at. Both deadlines are said in the portal settings, on the verification page, and in the terms — each reading the number off the command that enforces it. Also: the wordmark scan matched any element whose text merely BEGINS with the company name, which a paragraph of terms does. It looks for the lockup form now (no whitespace after the tag), which is what it was always about. The seven checkout tests in the parallel session's files were posting the old field name and now post the new one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
9da1358802 |
Stop charging VAT to the customers who owe us none
A verified EU business outside Austria gets reverse charge — rate 0, no VAT line, the note on the invoice — and was still charged 214,80 € for a 179,00 € package, because one Stripe Price carried the domestic gross for everybody. There is no VAT line on their document to reclaim, so it was a flat 20 % surcharge on exactly the customers who read their invoices, and the document then stated the whole 214,80 € as net at 0 %, so they self-accounted their own VAT on a base a fifth too large. A Stripe Price can ask who is buying after all: there are two per sellable thing now, the domestic gross and the bare net, both live on one Product, and the checkout picks by TaxTreatment. The rule is Austria B2B 20 %, other EU B2B without VAT — a domestic business still pays the gross, reclaims it as input tax, and the price on the website is still the price charged for them. - stripe:sync-catalogue mints and archives both halves of every pair, per plan and per module; `reverse_charge` on stripe_plan_prices/stripe_addon_prices says which is which, and joining it on subscriptions.stripe_price_id says which one a running contract is billed on. A rate change moves the gross Price and leaves the net one alone, because the net owes nothing to the rate. - A status that changes after the sale converges: a verification (or a lapse) makes stripe:reprice-subscriptions move the contract onto the other Price with PRORATE_NONE, because the term is already paid for. The module items follow the same way. - Downstream follows: the invoice total equals what was taken, the proof register expects the figure this customer is actually charged (so a correct reverse-charge sale is no longer flagged as a mismatch, and an overcharged one is), the setup fee obeys the same rule, and the booking page quotes what it will charge. - StripeClient gained activatePrice(). Archived Prices were brought back in our own table and left inactive at Stripe, so a rate that moved and moved back pointed the catalogue at a Price no checkout could be opened for. An unverified number is still charged the gross — an unchecked string must never be a discount — and where the net Price is missing the checkout refuses rather than reaching for the domestic one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |