CluPilotCloud/tests/Feature
nexxo 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>
2026-07-30 02:15:41 +02:00
..
Admin Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
Auth Ask whether they are a consumer, and let one change their mind 2026-07-29 21:06:06 +02:00
Billing Stop charging VAT to the customers who owe us none 2026-07-30 02:15:41 +02:00
Console Restart a machine, enforce the quota that was sold, end a route that ended 2026-07-29 18:28:28 +02:00
Mail Stop mailing the initial admin password, hold it in the panel until noted 2026-07-28 23:19:20 +02:00
Portal Skip the invoice for a full gift, keep it out of revenue, hide its price 2026-07-29 12:41:32 +02:00
Provisioning Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
BillingTest.php feat(portal): billing page — current plan, upgrades, extra storage, add-ons 2026-07-25 13:36:52 +02:00
CartTest.php fix(billing): normalise both sides of the VAT comparison; cast the timestamp 2026-07-26 09:36:07 +02:00
ComponentTest.php feat(portal): Fortify auth + Login/2FA/Dashboard + component kit 2026-07-25 01:20:25 +02:00
ConfirmInModalTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
ConnectionStateTest.php Recover silently from an expired session, and show a connection banner when offline 2026-07-28 21:36:21 +02:00
CustomDomainServingTest.php Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
CustomDomainTest.php Close the domain page to packages that may not have one 2026-07-29 16:20:21 +02:00
CustomerTwoFactorTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
DashboardTest.php Stop mailing the initial admin password, hold it in the panel until noted 2026-07-28 23:19:20 +02:00
DeliveryPromiseTest.php Take the order, park it, and say when it will be delivered 2026-07-29 18:50:46 +02:00
DeploymentRunsAsTheAppUserTest.php Fix nine defects in the provisioning pipelines 2026-07-30 01:34:55 +02:00
DisplayTimezoneTest.php Show times on the operator's clock, keep storing them in UTC 2026-07-27 17:32:21 +02:00
DoubleOptInTest.php Require a confirmed address before an account can use anything 2026-07-28 23:43:20 +02:00
DowngradeTest.php Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
EditInModalTest.php Fix a cancel button that did nothing, and fit the answers on one screen 2026-07-29 22:50:16 +02:00
EnsureCustomerActiveTest.php Satisfy Pint on the two new guard-boundary tests 2026-07-28 15:10:58 +02:00
ErrorPagesTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
ExampleTest.php chore: bootstrap CluPilot control-plane (Laravel 13, Docker stack) 2026-07-25 00:21:30 +02:00
GrossPricingTest.php Quote what a person pays, and tab the settings page 2026-07-29 19:18:10 +02:00
HostStepTest.php Let an update install host packages, without handing out root 2026-07-29 10:27:56 +02:00
IconLayoutTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
IdentitySeparationTest.php Write down that the console and the portal share no identity 2026-07-28 12:15:13 +02:00
ImpersonationTest.php Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
InstanceMetricsTest.php Measure what the template draws 2026-07-27 16:29:28 +02:00
LandingPriceSheetTest.php Quote what a person pays, and tab the settings page 2026-07-29 19:18:10 +02:00
MailTemplatesTest.php Actually issue the invoice when the money arrives 2026-07-29 03:25:56 +02:00
ModalHeightTest.php R24: a modal is never taller than the screen 2026-07-29 23:33:57 +02:00
MonitoringStatusSyncTest.php Move the console off /admin, give the status page its own address, and measure monitoring 2026-07-27 06:05:40 +02:00
NewDeviceWarningTest.php Recognise the devices an account signs in from, and warn about a new one 2026-07-28 23:28:34 +02:00
OfficialDomainsTest.php Re-apply what the last release commit silently reverted 2026-07-29 17:36:43 +02:00
PortalHostTest.php Answer on every name for the website, and send them all to one 2026-07-29 01:29:33 +02:00
PortalTabsTest.php Re-apply what the last release commit silently reverted 2026-07-29 17:36:43 +02:00
PublicSiteGateTest.php Stop a disabled operator from bypassing the hidden-site gate 2026-07-28 14:42:29 +02:00
ReleaseComparisonTest.php Stop the update agent dying on its own tag arithmetic 2026-07-29 00:00:17 +02:00
ReleaseVersionTest.php Move the console's identity out of the customer table 2026-07-28 10:31:43 +02:00
SeatsTest.php Replace native confirm() dialogs with the app's own modal pattern 2026-07-28 19:34:27 +02:00
SelfServiceOrderTest.php Ask for the express request to begin, and send it to Stripe 2026-07-29 21:19:03 +02:00
ServerMarketTest.php Take the order, park it, and say when it will be delivered 2026-07-29 18:50:46 +02:00
SessionListTest.php Show where an account is signed in, and let it sign the other places out 2026-07-28 23:38:17 +02:00
SettingsTest.php Stop charging for a service that has ended 2026-07-30 00:52:19 +02:00
SiteDesignSystemTest.php One wordmark, one typeface, and the address the server actually issues 2026-07-29 14:30:44 +02:00
StatusHistoryTest.php Let an incident be deleted, and start measuring whether the hosts answer 2026-07-29 15:16:48 +02:00
SupportRequestTest.php Editing in modals, an update button that is not gated on a stale reading, and a support page that is real 2026-07-27 17:55:49 +02:00
TrafficTest.php Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
TranslationParityTest.php Ask the tunnel gateway where it actually listens 2026-07-27 14:43:23 +02:00
WelcomeTest.php Give customers two-factor, and stop every button on the settings page reacting at once 2026-07-27 08:48:34 +02:00