# Payment gateway and tax compliance: install notes

M-Pesa (Daraja STK Push, C2B reconciliation) and KRA eTIMS (OSCU). Builds on Core, Inventory, CRM and Deals.
Needs PHP `bcmath`; queue worker and scheduler running (see step 4).

## 1. Copy files
Copy `app/`, `config/mpesa.php`, `config/etims.php`, `database/`, `resources/`, `routes/compliance.php`, `tests/` into the app.
These REPLACE earlier versions: `config/navigation.php`, `database/seeders/DatabaseSeeder.php`.

## 2. Wire up
```php
// end of routes/web.php
require __DIR__.'/compliance.php';
```
```php
// bootstrap/providers.php
App\Providers\ComplianceServiceProvider::class,
```
Two one-line additions to existing views:
- `resources/views/deals/show.blade.php`: add `@includeIf('deals._mpesa', ['deal' => $deal])` just before `@if ($deal->approvals->count())`.
- `resources/views/documents/_body.blade.php`: add `@includeIf('documents._fiscal', ['doc' => $doc])` just before the final "Issued by" paragraph. (The PDF view includes the same partial.)

```bash
php artisan migrate
php artisan db:seed --class=ComplianceSeeder
php artisan test --filter="MpesaTest|EtimsTest"
```

## 3. M-Pesa setup
```
MPESA_ENV=sandbox                      # production when live
MPESA_CONSUMER_KEY=...  MPESA_CONSUMER_SECRET=...      # from your Daraja app
MPESA_SHORTCODE=174379                 # paybill, or the till "store number" for Buy Goods
MPESA_TILL_NUMBER=                     # Buy Goods only
MPESA_PASSKEY=...
MPESA_TRANSACTION_TYPE=CustomerPayBillOnline           # or CustomerBuyGoodsOnline
MPESA_CALLBACK_SECRET=<long random string>             # openssl rand -hex 32
MPESA_CALLBACK_BASE_URL=https://dms.yourdomain.co.ke   # public HTTPS address of this app
MPESA_ALLOWED_IPS=                     # optional: Safaricom's current callback IPs, comma separated (ask Safaricom for the list)
```
- Run `php artisan mpesa:register-urls` once per shortcode and environment. Customers should pay a paybill with the **deal number** (e.g. DL-NBO-2026-00001) as the account number; it is shown on the pro-forma.
- Callback paths are `/pay/stk/callback/{secret}` and `/pay/c2b/...`: they avoid words Safaricom refuses in URLs. They need to be reachable from the internet over HTTPS (use a tunnel such as ngrok when testing locally).
- Going live needs Safaricom's approval of your shortcode, passkey and (for C2B) URL registration. Test every case in the sandbox first.

## 4. Running it
- `php artisan queue:work` (QUEUE_CONNECTION=database or redis, not sync, in production): callbacks, reconciliation and eTIMS sending run on the queue.
- Cron: `* * * * * cd /path/to/app && php artisan schedule:run >> /dev/null 2>&1`. Every minute it chases STK requests whose callback never arrived and sends eTIMS retries that are due.

## 5. eTIMS setup
1. Register for OSCU with KRA, and get your device serial number. Your system must be tested and approved by KRA before it can run in production. Use the sandbox until then.
2. **eTIMS > Devices and setup**: enter the PIN, branch id (00 = head office) and serial number for each branch, environment `sandbox`. Press *Initialise with KRA*: KRA returns the communication key, stored encrypted.
3. Press *Download item class codes*, search for the right class for passenger vehicles (and for add-ons such as insurance), and set in `.env`:
   `ETIMS_VEHICLE_CLASS_CODE=...` and `ETIMS_SERVICE_CLASS_CODE=...`. The system will not guess these.
4. Issue a test tax invoice. It is queued, vehicles and add-ons are registered with KRA, the vehicle is put into eTIMS stock, the sale is sent, and the vehicle is taken out of stock. Check **eTIMS** for the result and the full request/response log.
5. When satisfied, set `ETIMS_ENFORCE=true` (blocks tax invoices at branches with no device), switch the device to `production` and initialise it.

## How it fits together
**M-Pesa**
- STK Push: a person asks for a whole-shilling amount on the deal page; the customer gets a prompt; the page polls for the result. Safaricom's callback is stored first, then checked against the STK Query API (callbacks are unsigned) before the payment is posted through the normal payment service, so a receipt is issued and the deal confirmed when the deposit is in.
- Callbacks: wrong secret = 404, optional IP allow-list, every request logged (even garbage), duplicates harmless. If a callback never arrives, the Query API is asked; a paid-but-no-receipt case goes to a person rather than being guessed.
- C2B: each notification is stored once (TransID is unique) and matched to a deal by account number (any spelling of the deal number), then by full phone number if unambiguous. Overpayments, closed deals and unknown accounts wait on the **M-Pesa** page for a person to post or set aside. A payment seen both as STK and C2B is counted once.
- Automatic payments are recorded by an inactive "M-Pesa (automatic)" account that cannot sign in.

**eTIMS**
- Triggered when a tax invoice is issued. Figures come from the frozen invoice, converted to tax-inclusive lines, and must add up to the invoice total and tax.
- Result handling: accepted (receipt number, control unit signature, internal data, QR stored and printed on the invoice), failed (retried at 1, 5, 15, 60, 240, 720, 1440 minutes, then review), rejected (KRA refused the data: fix and press *Send again*), review (KRA reports overlapping data or retries ran out: check the eTIMS portal before resending).
- An invoice KRA accepted cannot be voided; it is cancelled with a **credit note** (receipt type R pointing at the original). The signature on the receipt is produced by KRA's control unit; this system does not sign invoices itself.
- Every call to KRA is logged with request, response, result code and timing. The communication key is never logged.

## Check before going live
- KRA fields and codes follow the OSCU Specification Document v2.0 (April 2023). KRA may have revised it; compare `EtimsPayloadBuilder`, `EtimsItemService` and `config/etims.php` with the version KRA gave you, and with sandbox results. Items to confirm in the sandbox: item code format, stock-in type for imported used vehicles (`stock_in_type` 01 vs 02), the status code used on credit notes, how mixed payment methods should be reported, and the receipt QR address.
- Confirm with your accountant how VAT applies to the vehicles you sell, and that trade-in credits are reported the way KRA expects (here they are treated as part-payment, so the invoice is for the full price).
- Daraja details were checked against public documentation and third-party references, not a live Safaricom account.
- Not run in the authoring environment (no PHP): run migrations and tests first. The tests fake Safaricom and KRA; they prove our side of the contract, not theirs.

## Known gaps
Refunds of M-Pesa overpayments (B2C reversal), dashboard widgets, a daily reconciliation report, eTIMS purchase and stock-adjustment transactions beyond vehicle stock in/out, credit notes for part of an invoice, and an eTIMS stock-in when a vehicle is returned after a credit note.
