Billing & renewal
The 1-month trial after a 10% deposit, the 3-year contract with a year-1 exit, annual billing, renewal, Cancel trial, Cancel renewal, no refunds for the year already started, and what each order stores.
Every package starts with a 1-month trial after a 10% deposit, then a 3-year contract, billed once a year in advance, per vessel. You can leave in year 1, before the first renewal. Each invoice is one billed year (12 months). The first charge also carries the one-off setup per vessel, Safety Vision setup and installation when the cameras are bought, and the optional gateway kit. Prices come from @pruva/pricing; see Pricing.
Billing notice
The free trial
- 1 month, one per customer. A workspace that has already had a trial pays the full first charge on the day it orders.
- 10% deposit today. At checkout the buyer chooses a payment method and ticks the consent box. The deposit is taken that day. It is not refunded.
- The rest of the first charge when the month ends: the order's
trial.firstChargeOn. The trial runs from the purchase day throughtrial.end, the day before. - The first billed year (12 months) starts after the trial, on the first-charge day. That year is non-refundable from then on. The contract is 3 years. You can leave in year 1, before the first renewal.
- Cancelling during the trial stops the rest of the charge. The deposit is not refunded. See Cancel trial.
Every pricing card on the site says "1-month trial · 10% deposit".
Payment schedule
scheduleFrom(purchase, firstChargeUsd, withTrial) in @pruva/pricing computes the schedule, and scheduleLabel() turns it into the line the checkout shows before the order button. For Operations with three Pro modules on 10 vessels, ordered on 16 October 2026:
| Order | Free trial | Today | First charge | Term | Renews on | Checkout shows |
|---|---|---|---|---|---|---|
| With trial | 2026-10-16 to 2026-11-15 | $8,945 | $89,450 on 2026-11-16 | 2026-11-16 to 2027-11-15 | 2027-11-16 | Today: $8,945 deposit · Remainder on Nov 16, 2026 |
| Trial already used | none (already used) | $89,450 | $89,450 on 2026-10-16 | 2026-10-16 to 2027-10-15 | 2027-10-16 | Today: $89,450 · Renews on Oct 16, 2027 |
Before the order button the checkout shows, for an eligible workspace, "Today: $… deposit · Remainder on …", the trial dates, the billed-year start, end and renewal dates, and the first-charge amount. The button says Start 1-month trial.
Trial eligibility
The checkout asks the app GET /api/trial-eligibility?workspace=… (short timeout) whether the workspace can still have a trial.
- Trial already used. The checkout says "Your free trial was already used", shows the charge-today schedule, builds the order without a trial, and the button says Place demo order.
- App unreachable. The checkout assumes the trial is unused and says so in small text. The app checks again when the order arrives.
If the app answers 409 trial_used (for example, a second tab used the trial first), nothing is ordered or charged. The checkout shows the message, switches to the no-trial schedule, and unticks the consent box. The buyer must tick it again and place the order without a trial. The site never retries without a trial on its own.
Term dates
A term has three dates. The checkout shows all three before you buy, and the order carries them.
| Date | Meaning |
|---|---|
| Start | The first-charge day, after the trial. Without a trial: the day you order. |
| End | The last day of the billed year: 12 months after the start, minus one day. |
| Renews on | The day after the end. The next year is charged on this day. |
Example: a term that starts on 23 October 2026 ends on 22 October 2027 and renews on 23 October 2027. The dates come from termFrom() in @pruva/pricing and use UTC days.
Cancel trial
During the trial, an owner opens Settings → Modules & billing in the app, where the module shows the trial days left and a Cancel trial button.
- The rest of the first charge is not taken. The deposit is not refunded.
- The trial's modules lock straight away.
- The trial still counts as used (one per customer).
Only the owner can cancel, and only while the trial runs. Each cancel is written to the audit log as billing.cancel_trial with "(no charge)".
The remainder charge
A job takes the rest of the first charge when the trial month ends: runTrialCharges() in @pruva/core (demo mode runs it lazily) and its SQL twin public.pv_run_trial_charges() (database mode, service role only). For each order whose firstChargeOn has arrived, it records that charge once and moves the order's modules from trial to active.
- Idempotent. It sets
charged_atonce. A second run on the same day, or any later day, charges nothing again. - Never charges a cancelled trial. Orders with
trial_cancelled_atset are skipped. - In this build there is no payment provider, so "charging" only records the charge. A real build takes the charge from the payment-method reference on the order (a Stripe invoice, or capturing a 3-D Secure pre-authorisation).
Renewal
A plan renews for another billed year (12 months) unless renewal is cancelled before the renewal date. Leaving in year 1 is cancelling before the first renewal. Prices can change only at renewal, and owners are told before the renewal date.
Cancel renewal
After the trial, an owner opens Settings → Modules & billing in the app and taps Cancel renewal.
- It stops the next renewal and nothing else.
- Access continues to the end of the term you paid for.
- Nothing is refunded.
No refunds
After the trial, there are no refunds for the year already started, including unused months, unused modules or a downgrade. After the billed year ends, data is kept for 90 days so you can export it, then deleted.
Payment method: a reference, never card data
The checkout asks for a payment method; the step is required. This build has no payment provider, so it never shows card-number, expiry or CVC fields and never accepts card data. The only option is "Demo card · no real card (Stripe test mode in production — verify before launch)". The server refuses any request that carries card fields.
A real build collects the card with Stripe Elements or a 3-D Secure pre-authorisation, so the card never touches our servers. Either way, the order carries only a reference:
| Field | Demo value |
|---|---|
payment.provider | demo (or stripe in a real build) |
payment.reference | pm_demo_visa_4242; must match pm_… |
payment.label | Demo card · no real card; the schema refuses a label that contains a card number |
Consent stored on the order
At checkout the buyer must tick a box. It starts unticked, and the order button stays disabled until both the payment method is chosen and the box is ticked. The box says exactly:
I agree to a 10% deposit today, which opens a 1-month trial, followed by a 3-year contract, billed annually in advance per vessel at the price of my billing company's country tier, plus the one-off setup fee, any support plan, any Safety Vision camera line and any onboard hardware shown. Safety Vision includes its camera kit, warranty and replacement, which stay Pruva's property. Hardware is a separate line; Safety Vision and hardware are one USD price in every country and are not discounted by the country tier. I can leave in year 1, before the first renewal. The rest of that first charge is taken when the trial month ends unless I cancel before then. The deposit is not refunded. After the trial there are no refunds for the year already started, and I can cancel renewal at any time before the renewal date.
The server refuses any order without consent or without a payment method, whatever the browser sends. The order the site signs and sends to the app (StoreOrderSchema in @pruva/core) carries:
| Field | What it holds |
|---|---|
consent.accepted | Always true; an order without consent is never sent. |
consent.version | The version of the consent text, now 2026-10-20.2. |
consent.at | When the ticked form reached the server (server clock, ISO time). |
trial | start, end, firstChargeOn; or null when the trial was already used. The trial is 1 month. The billed year starts on firstChargeOn. |
chargeTodayUsd | The 10% deposit with a trial; the first charge (annualUsd + oneOffUsd) without one. |
payment | The payment-method reference above. |
term.start, term.end, term.renewsOn | The billed-year dates above. The app checks they span 12 months. The contract is 3 years, with an exit in year 1. |
sku, modules, vessels | The package or pack, every module it switches on (the core, the Pro modules, and cameras with Safety Vision) and the number of vessels. support is the support plan. |
annualUsd | The year's subscription for all vessels. The app checks it against the price list. |
oneOffUsd | Setup per vessel, Safety Vision setup and installation, and the optional gateway kit. |
firstChargeUsd | annualUsd + oneOffUsd. The deposit is taken today; the rest is taken when the trial month ends (or the whole amount today without a trial). |
The success page shows the order id, the items, the trial month, the deposit taken today and the remainder date (or the amount charged today when there is no trial), the billed-year dates, "Payment method: Demo card · no real card", "Consent recorded: version … at …", and a link to Settings → Modules & billing. Cancelling the trial there stops the rest of the charge. The deposit is not refunded.
Module accents
Each module has its own accent colour, from @pruva/brand/tokens. The app, the site and these docs use it for borders, chips and icons only, never for body text. Accent-coloured text switches to a darker shade in the light theme so it meets WCAG AA. The brand itself stays monochrome, with brass for primary buttons.