# Installing CareBill

Tested on PHP 8.3 and MySQL 8 / MariaDB 10.11. Ubuntu 24.04 assumed below.

## 1. Requirements

```bash
apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-zip php8.3-xml php8.3-mbstring
```

`mbstring` is optional — there is a fallback in `app/core.php` — but install it if you can.
`zip` and `xml` are needed to read Excel statements. `curl` is needed for SES, WhatsApp and calling.

For PDF invoices, install one of these. Without either, invoices still email as clean HTML.

```bash
apt install wkhtmltopdf          # lighter
# or
apt install chromium-browser     # better CSS fidelity
```

## 2. Files

Put the project outside the web root and point the vhost at `public/` only.

```bash
mkdir -p /var/www/carebill && cd /var/www/carebill
# copy the project here
chown -R www-data:www-data storage
chmod -R 775 storage
```

Nginx:

```nginx
server {
    server_name billing.caresoft.co.in;
    root /var/www/carebill/public;
    index index.php;

    location / { try_files $uri $uri/ /index.php?$query_string; }
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    location ~ /\. { deny all; }
}
```

Apache: point DocumentRoot at `public/` and allow `.htaccess` overrides, or nothing else needed.

**Put TLS on it before it holds a single client record.** Session cookies mark themselves
secure automatically once HTTPS is on.

## 3. Database

```bash
mysql -e "CREATE DATABASE carebill CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -e "CREATE USER 'carebill'@'localhost' IDENTIFIED BY 'a-long-password';"
mysql -e "GRANT ALL ON carebill.* TO 'carebill'@'localhost'; FLUSH PRIVILEGES;"

mysql carebill < sql/schema.sql
mysql carebill < sql/seed.sql
```

The seed loads the CSPL brand with your real GSTIN, CIN, PAN, Udyam number, bank details,
terms and exclusions annexure, plus four brand shells (Digital IPD, Digital OPD, Cloud,
Medicircle), about 26 billing items, 18 message templates and 17 reminder rules.

## 4. Configuration

```bash
cp config/config.sample.php config/config.php
```

Set the database password, the application URL, and **change `cron_key`** to a long random
string. Then in the application, under Setup → Settings, fill in SES or SMTP credentials,
the WhatsApp phone number ID and token, the send window and the escalation addresses.

## 5. First login

```
admin@caresoft.co.in / Caresoft@123
```

It forces a password change on first sign-in. Change it, then add the real users under
Setup → Users. Delete or disable the seeded `accounts@caresoft.co.in` account if it is not
going to be used.

## 6. Cron

```cron
30 6 * * *    php /var/www/carebill/cron/daily.php  >> /var/www/carebill/storage/logs/cron.log 2>&1
*/10 * * * *  php /var/www/carebill/cron/queue.php  >> /var/www/carebill/storage/logs/cron.log 2>&1
```

`daily.php` drafts invoices, sends the pre-billing alerts, walks the dunning ladder and
handles renewals. `queue.php` drains the outbox — it respects the send window, so running
it every ten minutes around the clock is fine.

On shared hosting without shell cron, hit these URLs instead:

```
https://billing.caresoft.co.in/index.php?r=cron/run&job=daily&key=YOUR_CRON_KEY
https://billing.caresoft.co.in/index.php?r=cron/run&job=queue&key=YOUR_CRON_KEY
```

Check Setup → Settings that the last run is recent. If the nightly job stops, nothing
sends, and that is exactly the failure you are trying to get away from.

## 7. Loading the existing book

Use **Setup → Import data** in the application. Upload the workbook as it is — sheet
selection, column matching and a trial run are all on screen, and nothing is written until
you press Load it.

Load in this order:

1. **Clients** — every hospital, with the person the bill should go to.
2. **Bank payer names** — the names they pay under in the statement. Twenty minutes here
   means those credits match themselves from the first statement onward.
3. **Services** — one row per billable line. An AMC, a cloud rental and a backup plan are
   three rows, because each has its own period, its own due date and its own reminders.
4. **Outstanding invoices** — what is already being chased.

What the importer handles without help:

| In your sheet | What happens |
|---|---|
| `01/04/2024 To 31/03/2025` | split into two dates, cycle worked out from the length |
| `45383` | read as an Excel date serial |
| Headings on row 6 under a legend | pick the heading row from the dropdown |
| A row already marked paid | skipped, not loaded as outstanding |
| A footnote or total row | skipped, with the reason shown |
| The same sheet loaded twice | nothing duplicated |

For bulk or scripted loads the same engine runs from the command line:

```bash
# always trial first — this saves nothing
CAREBILL_ENV=test php tools/import_legacy.php services AMC_Accounts_Followup.xlsx --sheet=16 --item=AMC
CAREBILL_ENV=test php tools/import_legacy.php services AMC_Accounts_Followup.xlsx --sheet=16 --item=AMC --commit

# a sheet whose headings are not on the first row
CAREBILL_ENV=test php tools/import_legacy.php opening Payment_Followup.xlsx --sheet=5 --header=5 --item=AMC
```

Options: `--sheet=N`, `--header=N`, `--brand=CODE`, `--item=CODE`, `--cycle=yearly`,
`--gst=18`, `--no-create`, `--map=field:column`, `--commit`.

### Two things to check by eye after loading

**Which column is the rate.** On a sheet with both "Charges/Month" and a quarterly
"Amount", only you know which one the service should bill. The trial run shows sample
values against each match — use it.

**Clients with no email.** Anything imported from a sheet without an email column has
nowhere to send a reminder. The client list has a "missing email contact" filter; work
through it before the nightly job starts running.

**Load outstanding invoices before turning cron on**, and check them, because that is the
point at which CareBill starts writing to clients on its own.

## 7b. Opening the client portal

The portal lives at `/portal.php` on the same site. Nothing extra to deploy.

1. **Settings → Client portal** — switch it on and set the portal address
   (`https://billing.caresoft.co.in/portal.php`). It goes into the invitation email.
2. **TLS is not optional here.** Clients sign in with an emailed code; without HTTPS that
   code crosses the network in the clear.
3. Check a client has at least one contact with an email address. Contacts have a
   `portal_access` flag and clients a `portal_enabled` flag — both default to on.
4. From a client record, **Invite to portal** emails the invitation.

Start with five friendly clients before inviting the book. The first thing you will learn is
how many of your contact addresses are stale.

If you are upgrading an existing install rather than starting fresh:

```bash
mysql carebill < sql/portal.sql
```

That adds the portal tables and the two flags. `schema.sql` already contains them for new
installs, so run one or the other, never both.

## 7a. Online payments

1. **Settings → Online payments.** Choose Razorpay or Cashfree, set the key id and secret,
   and set a webhook secret.
2. Put the same webhook secret in the gateway dashboard, pointed at
   `https://your-domain/webhook.php`. Subscribe to payment captured and payment link paid.
3. Try **sandbox** first: it exercises links, webhooks, settlement and expiry without touching
   a real gateway.
4. The Pay button appears automatically in the due and overdue reminder templates through the
   `{pay_button}` placeholder. Remove it from a template if you do not want it there.

`webhook.php` takes no session and no CSRF token — the signature is the authentication. It
must be reachable from the internet over HTTPS, and it returns 500 on an internal error so
the gateway retries rather than dropping the payment.

For an existing install:

```bash
mysql carebill < sql/gateway.sql
```

## 7bb. Billing outside India

1. **Settings → Currency and exports.** Base currency stays INR. Record the LUT/ARN number
   and its expiry if you export under LUT.
2. **Exchange rates.** Record a rate per currency. Use the CBIC notified rate for the invoice
   date and mark the source as such — it is the one an auditor will ask about.
3. On the client: set the country, the billing currency and the supply treatment.
4. On each service: the currency it is billed in. Services in different currencies cannot go
   on one invoice, and CareBill refuses rather than picking one.

Billing in a currency with no rate on file fails loudly. That is deliberate.

For an existing install:

```bash
mysql carebill < sql/currency.sql
```

Adds the currency tables, the dual-currency columns, and rebuilds the outstanding view.
Existing rows default to INR at a rate of 1, so nothing already on file changes meaning.

## 7c. Turning on e-invoicing

Only if your CA confirms it applies — aggregate turnover above Rs. 5 crore in any year since
2017-18, permanently once crossed.

**Before anything else, settle the invoice numbering.** The IRP allows 16 characters for a
document number. `CSPL-P/SUB-001/26-27` is 20 and will be rejected every time. Change the
prefixes under Setup → Brands to something like `CSPL/2627/` before the first IRN is
requested; an invoice number cannot be altered once reported.

1. **Settings → GST e-invoicing.** Switch on, choose how to report, set the reporting GSTIN,
   and set the 30-day rule if turnover is Rs. 10 crore or more.
2. Fill in client PIN codes and addresses. The IRP checks both. The e-invoicing queue lists
   every invoice that would fail and why, so work through that list before switching on.
3. Start on **sandbox** to see the whole flow — queue, IRN, printed QR, cancellation —
   without touching the real portal. Invoices produced this way are stamped SANDBOX.
4. Move to **manual**, **nic** or **gsp** when credentials are in place.

For an existing install:

```bash
mysql carebill < sql/einvoice.sql
```

That adds the `einvoices` table, a `pincode` column on clients, and the settings. Fresh
installs get it from `schema.sql`.

The nightly job reports anything unreported once a non-manual driver is configured, stops
after three rejections on the same invoice, and raises a task instead.

## 7f. WhatsApp

1. **Settings → WhatsApp and calling.** Phone number id, permanent token, then a verify token
   and the app secret of your own choosing.
2. In the Meta dashboard, set the webhook to `https://your-domain/wahook.php` with that verify
   token, and subscribe to the `messages` field.
3. Get your utility templates approved and put each approved name on the matching template
   record under Setup → Templates.

Without the webhook, replies and opt-outs never reach CareBill — which means somebody who
asked you to stop gets messaged again, and that is what costs you the number.

For an existing install:

```bash
mysql carebill < sql/whatsapp.sql
```

## 7e. Bounce handling

1. **Settings → Deliverability.** Set a webhook key.
2. Point the provider at `https://your-domain/mailhook.php?k=YOUR_KEY`.
   For SES, create an SNS topic for Bounce, Complaint and Delivery notifications and subscribe
   that URL — CareBill confirms the subscription itself.
3. On a new sending domain, set the warm-up start date to today.

Without this, bounces are invisible: dead addresses stay on the list, get written to every
month, and the bounce rate climbs until the provider acts.

For an existing install:

```bash
mysql carebill < sql/deliverability.sql
```

## 7d. Locking the sign-in down

1. Each user opens **Your security** and turns on two factor. Admins first.
2. **Settings → Sign-in security** can then require it of all admins.
3. Everyone saves their recovery codes somewhere other than the phone.

For an existing install:

```bash
mysql carebill < sql/security.sql
```

## 7g. The weekly board email

**Settings → When and to whom** takes a management address and the weekday for the digest.
It goes out with the nightly job. Set the day to Never to switch it off.

## 7h. Integrity and backups

**Settings → Backups** turns on the nightly dump and how many to keep. It needs `mysqldump`
on the server, and reports plainly if it is missing.

The integrity check runs nightly on its own. **Setup → Integrity** shows what it found, offers
to recalculate anything safely repairable, and carries the audit trail of who changed what.

Keep the separate off-machine backup from section 9 as well. A backup on the same server does
not survive losing the server.

## 7i. Indexes

```bash
mysql carebill < sql/indexes.sql
```

Safe to re-run; each index is skipped if it is already there. Fresh installs get them from
`schema.sql`. Without them the nightly run slows down as the book grows, which is the kind of
problem that arrives quietly a year in.

## 7j. Overlapping runs

Nothing extra to configure — the nightly job and the queue runner take database locks and
stand aside if a previous run is still going, so a cron entry that fires while the last one is
still working is safe. If a run hangs, the lock dies with the process; Setup → Readiness says
whether one is held right now.

This does mean a run can be skipped rather than queued. That is the intended trade: a missed
nightly run catches up the next night, because the ladder is catch-up safe.

## 7k. Revenue recognition

On by default. Schedules are built as invoices are issued, and the nightly job catches
anything that has none — including everything loaded from your old sheets.

Ask your CA two things before relying on the figures: whether they want revenue spread by
days (what CareBill does) or by whole calendar months, and from which month you intend to
close periods. Then close each month once it has been signed off, so the figures stop moving.

For an existing install:

```bash
mysql carebill < sql/revenue.sql
```

## 7l. TDS credits

Put each deducting client's **TAN** on their record — it is the only field your books and
their 26AS reliably share, and without it a 26AS line cannot be matched back.

Chasing is on by default and only starts once a quarter's certificate due date has passed.
Turn it off in settings if your team would rather ask by phone.

For an existing install:

```bash
mysql carebill < sql/tds.sql
```

## 7m. Approvals

On by default. **You need at least two active admins** — nobody can approve their own request,
so with one admin nothing that needs approval can ever be approved. Ideally the second is
someone outside the accounts team.

Adjust the thresholds under Settings → Approvals. Bank changes and credit notes are set to
always, and those two are the ones worth leaving alone.

For an existing install:

```bash
mysql carebill < sql/approvals.sql
```

## 7n. Roles

Give each person the narrowest role that fits. Your CA gets **Auditor** — every figure and
export, no way to change anything. Whoever only chases gets **Collector**, which cannot write
off or credit a balance.

For an existing install:

```bash
mysql carebill < sql/roles.sql
```

Existing users keep the old "user" role and behave as Accounts until you change them.

## 7o. Invoice numbering — before the first live invoice

Numbers read `CSPL/26/09/0001`. Fresh installs already use this. For an existing install:

```bash
mysql carebill < sql/numbering.sql
```

**Run it before the first live invoice.** Numbers cannot be changed once issued, and switching
series part-way through a financial year is a question for your CA. Setup → Readiness confirms
every brand's numbers fit the portal's 16-character limit.

## 7q. CCAvenue

Settings → Payments: provider CCAvenue, then merchant id, access code and working key from
the CCAvenue dashboard (Settings → API Keys), mode Test until proven. Register the site and
`/ccavenue.php` as the return address with CCAvenue. The five-minute queue cron also runs
the sweep that confirms payments whose browser never came back.

For an existing install:

```bash
mysql carebill < sql/ccavenue.sql
```

## 7r. Razorpay alongside CCAvenue, targets, SMS, retention, Tally

For an existing install, in this order:

```bash
mysql carebill < sql/razorpay_both.sql
mysql carebill < sql/pendings.sql
```

Razorpay: Key id, Key secret and Webhook secret under Settings → Payments, with the webhook in
Razorpay's dashboard pointed at `/webhook.php` for the `payment_link.paid` event. Tick both
methods under "Offer clients".

## 7p. The quick way

For a new server, `deploy/install.sh` does sections 1 to 4 in one go, and `deploy/GOLIVE.md`
takes it from there. The manual steps above are still the reference for upgrades and for
anything unusual about your server.

## 8. Verify

Test tools refuse to run unless `config/config.php` says `'env' => 'test'` **and** the command
is prefixed with `CAREBILL_ENV=test`. Run them only on a scratch copy. On the live server,
use `php tools/smoke_test.php` — it only reads.

```bash
CAREBILL_ENV=test php tools/e2e_test.php       # 61 checks on billing, dunning, matching
CAREBILL_ENV=test php tools/portal_test.php    # 47 checks on the portal, mostly access control
CAREBILL_ENV=test php tools/einvoice_test.php  # 55 checks on e-invoicing and the QR encoder
CAREBILL_ENV=test php tools/currency_test.php  # 57 checks on multi-currency and export invoicing
CAREBILL_ENV=test php tools/gateway_test.php   # 51 checks on payment links and webhook security
CAREBILL_ENV=test php tools/security_test.php  # 49 checks on two-factor sign-in
CAREBILL_ENV=test php tools/deliverability_test.php  # 52 checks on bounce handling and pacing
CAREBILL_ENV=test php tools/whatsapp_test.php  # 55 checks on consent, the 24h window and webhooks
CAREBILL_ENV=test php tools/mis_test.php       # 48 checks on the management numbers (clears the ledger)
CAREBILL_ENV=test php tools/integrity_test.php # 47 checks on integrity, backups and brand scoping (clears the ledger — run last)

# and, on a scratch database only, the scale test:
CAREBILL_ENV=test php tools/seed_load.php 1000 6   # builds a book the size of yours
CAREBILL_ENV=test php tools/perf_test.php          # 20 time and query budgets against it
CAREBILL_ENV=test php tools/concurrency_test.php   # 24 checks with real parallel processes
CAREBILL_ENV=test php tools/revenue_test.php       # 55 checks on revenue recognition (clears the ledger)
CAREBILL_ENV=test php tools/tds_test.php           # 56 checks on TDS credits (clears the ledger)
CAREBILL_ENV=test php tools/approvals_test.php     # 55 checks on maker/checker approvals
CAREBILL_ENV=test php tools/roles_test.php         # the route-by-role access matrix
CAREBILL_ENV=test php tools/ccavenue_test.php
CAREBILL_ENV=test php tools/twogateway_test.php
CAREBILL_ENV=test php tools/pendings_test.php
CAREBILL_ENV=test php tools/numbering_test.php     # invoice numbering and the portal's rules
``` Run this against a scratch database, never against live — it writes real rows.

Then **Setup → Readiness** in the application, and clear everything it marks *fix first*.
`PILOT.md` covers the rollout itself.

Then:

1. Setup → Settings → send a test email.
2. Create one client and one service with a billing date a few days out, and confirm it
   appears on the dashboard under "due for billing".
3. Import one bank statement and match one credit.

## 9. Backups

```cron
0 2 * * * mysqldump carebill | gzip > /backup/carebill-$(date +\%F).sql.gz
```

Keep `storage/invoices` too — issued invoice PDFs are records.
