> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lekalao.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending Lekalao

> The points made for plugging in pieces of your own.

Lekalao is a Laravel application: one service provider is enough to replace a piece with your own. Keep your extensions in a provider of their own (`app/Providers/InstallationServiceProvider.php`, say), so that upgrades do not overwrite them.

## The readers' countries

Lekalao ships with no geolocation database: they are heavy, they age quickly and their licences vary. Without one, the **Country** column of the statistics stays empty; everything else works.

To fill it, implement `App\Domain\Analytics\Contracts\GeoLocator`:

```php app/Support/MaxMindGeoLocator.php theme={null}
namespace App\Support;

use App\Domain\Analytics\Contracts\GeoLocator;
use GeoIp2\Database\Reader;
use Throwable;

class MaxMindGeoLocator implements GeoLocator
{
    public function __construct(private readonly Reader $reader) {}

    public function country(?string $ip): ?string
    {
        if ($ip === null) {
            return null;
        }

        try {
            return $this->reader->country($ip)->country->isoCode;
        } catch (Throwable) {
            return null;
        }
    }
}
```

```php app/Providers/InstallationServiceProvider.php theme={null}
use App\Domain\Analytics\Contracts\GeoLocator;
use App\Support\MaxMindGeoLocator;
use GeoIp2\Database\Reader;

public function register(): void
{
    $this->app->singleton(GeoLocator::class, fn () => new MaxMindGeoLocator(
        new Reader(storage_path('app/GeoLite2-Country.mmdb')),
    ));
}
```

The IP address is never stored: it serves to find the country, then it is forgotten. Return a two-letter code (`CM`, `FR`) or `null`.

## A payment gateway

`App\Domain\Billing\Gateways\BillingGateway` says what a gateway has to know how to do:

| Method                                 | Role                                                                                   |
| -------------------------------------- | -------------------------------------------------------------------------------------- |
| `name()`                               | The name recorded on every payment.                                                    |
| `checkout(Payment, string $returnUrl)` | Starts a payment and returns a `Checkout`: a reference and where to send the customer. |
| `status(Payment)`                      | Asks the gateway where a payment stands (the **Check again** button).                  |
| `verifyWebhook(Request)`               | Checks that a call really comes from the gateway.                                      |
| `readWebhook(Request)`                 | Pulls the reference and the new status out of a call, or `null`.                       |

The gateway's calls arrive at `POST /webhooks/billing`. Wire yours into `AppServiceProvider`, where `LEKALAO_BILLING_GATEWAY` is read, or redefine the binding:

```php theme={null}
$this->app->bind(BillingGateway::class, fn () => new MyGateway(config('services.my_gateway')));
```

## A sending provider

The providers on offer (SMTP, Amazon SES, Postmark, Mailgun, SendGrid, Brevo, Resend) are described in `App\Domain\Settings\Enums\MailerTransport`: the fields of the form, the Symfony Mailer transport configuration, how feedback is read. For a new provider, add a case to that enum, following one of the existing ones.

## The interface languages

Add a `lang/{code}.json` file holding every string of `lang/fr.json`, and declare the language in `config/lekalao.php`, key `locales`.

<Note>
  These extension points stay stable from one version to the next. The rest
  of the code may change: avoid editing Lekalao's own files.
</Note>
