laratables / monta-partner-api
Laravel SDK for the Monta Partner API — authentication, token caching, and a fluent HTTP client.
Requires
- php: ^8.2
- illuminate/cache: ^11.0|^12.0|^13.0
- illuminate/http: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
- nesbot/carbon: ^3.0
Requires (Dev)
- mockery/mockery: ^1.6
- orchestra/testbench: ^9.0|^10.0|^11.0
- phpunit/phpunit: ^11.0|^12.0
README
A Laravel 11/12/13 SDK for the Monta Partner API. Handles OAuth token exchange, automatic caching & refresh, and exposes a clean fluent client for every HTTP verb.
Installation
composer require laratables/monta-partner-api
The service provider and Monta facade are auto-discovered via Laravel's
package discovery. No manual registration needed.
Publish the config (optional):
php artisan vendor:publish --tag=monta-config
Configuration
Add to your .env:
MONTA_CLIENT_ID=your-client-id MONTA_CLIENT_SECRET=your-client-secret # Optional — these are the defaults: MONTA_BASE_URL=https://api.monta.com MONTA_API_VERSION=v2024-01-18 MONTA_CACHE_STORE=default # any Laravel cache store MONTA_HTTP_TIMEOUT=30 MONTA_HTTP_CONNECT_TIMEOUT=10
Usage
Dependency Injection (recommended)
use Monta\PartnerApi\Contracts\MontaClientInterface; class ChargeController extends Controller { public function __construct(private MontaClientInterface $monta) {} public function index(): JsonResponse { $data = $this->monta->get('/charge-points', ['pageSize' => 25])->json(); return response()->json($data); } public function start(int $id): JsonResponse { $result = $this->monta->post("/charge-points/{$id}/start")->json(); return response()->json($result); } }
Facade
use Monta\PartnerApi\Facades\Monta; $chargePoints = Monta::get('/charge-points', ['pageSize' => 50])->json(); $charge = Monta::post('/charges', ['chargePointId' => 123])->json(); $updated = Monta::patch('/charge-points/456', ['name' => 'Bay 1'])->json(); Monta::delete('/webhooks/789');
Accessing any Monta API endpoint
The client works with any endpoint from the Monta API docs — just pass the path and any parameters:
// Charges $charges = $this->monta->get('/charges', ['pageSize' => 25, 'page' => 1])->json(); $charge = $this->monta->post('/charges', ['chargePointId' => 123])->json(); // Teams $teams = $this->monta->get('/teams')->json(); $team = $this->monta->get('/teams/456')->json(); // Webhooks $webhook = $this->monta->post('/webhooks', [ 'url' => 'https://yourapp.com/webhooks/monta', 'events' => ['charge.started', 'charge.stopped'], ])->json(); $this->monta->delete('/webhooks/789'); // Wallets $wallet = $this->monta->get('/wallets/123')->json(); // Any other endpoint — same pattern $this->monta->patch('/charge-points/456', ['name' => 'Bay 1']);
Working with responses
Every method returns a standard Laravel Illuminate\Http\Client\Response object:
$response = $this->monta->get('/charges'); $response->json(); // full decoded array $response->json('data'); // single key from the response $response->json('data.0.id'); // nested key using dot notation $response->collect('data'); // returns a Laravel Collection $response->status(); // HTTP status code e.g. 200 $response->successful(); // true if 2xx
Domain resource classes
Copy ChargePointsResource as a template for each API domain (Charges, Teams,
Wallets, Webhooks…):
use Monta\PartnerApi\Http\Resources\ChargePointsResource; // Register in a service provider: $this->app->bind(ChargePointsResource::class, fn($app) => new ChargePointsResource($app->make(MontaClientInterface::class)) ); // Use in a controller: public function __construct(private ChargePointsResource $chargePoints) {} public function index(): JsonResponse { return response()->json($this->chargePoints->list(['pageSize' => 50])); }
Error Handling
use Monta\PartnerApi\Exceptions\MontaApiException; use Monta\PartnerApi\Exceptions\MontaAuthenticationException; try { $result = $this->monta->post('/charges', $payload)->json(); } catch (MontaAuthenticationException $e) { // Bad credentials, or the API returned 401 // The cached token is automatically cleared; the next request will re-authenticate Log::critical('Monta auth failed', ['error' => $e->getMessage()]); } catch (MontaApiException $e) { // Any other non-2xx response Log::error('Monta API error', [ 'status' => $e->getStatusCode(), 'body' => $e->getResponse()->json(), ]); }
Token Caching
Access tokens are cached in the Laravel cache store defined by MONTA_CACHE_STORE.
They are proactively refreshed 30 seconds before expiry to avoid clock-skew issues.
A 401 response automatically evicts the cached token so the next call re-authenticates
transparently.
Testing
Running the test suite
composer test # or directly: ./vendor/bin/phpunit
Mocking in your application tests
Because everything is bound against MontaClientInterface, swapping in a mock
is straightforward:
use Monta\PartnerApi\Contracts\MontaClientInterface; // Using Laravel's built-in mock helper (Mockery under the hood): $this->mock(MontaClientInterface::class, function ($mock) { $mock->shouldReceive('get') ->with('/charge-points', \Mockery::any()) ->andReturn(new \Illuminate\Http\Client\Response( new \GuzzleHttp\Psr7\Response(200, [], json_encode(['data' => []])) )); }); // Or use Http::fake() to intercept at the HTTP layer: Http::fake([ 'https://api.monta.com/auth/token' => Http::response(['accessToken' => 'tok', 'expiresIn' => 3600]), 'https://api.monta.com/charge-points' => Http::response(['data' => []]), ]);
Package Structure
src/
├── Contracts/
│ └── MontaClientInterface.php Type-hint this in your code
├── Exceptions/
│ ├── MontaApiException.php Non-2xx API responses
│ └── MontaAuthenticationException.php Auth / token failures
├── Facades/
│ └── Monta.php Monta:: facade
├── Http/
│ ├── MontaClient.php Core client (auth + all verbs)
│ └── Resources/
│ └── ChargePointsResource.php Example domain resource — copy this pattern
├── Support/
│ └── MontaToken.php Token value object with expiry
└── MontaServiceProvider.php Auto-discovered service provider
config/
└── monta.php
tests/
├── TestCase.php Orchestra Testbench base
├── Unit/
│ ├── MontaTokenTest.php Token parsing & expiry logic
│ ├── ExceptionsTest.php Exception message / code derivation
│ ├── MontaClientAuthTest.php Token fetch, caching, refresh
│ ├── MontaClientHttpTest.php HTTP verbs, headers, error handling
│ └── ChargePointsResourceTest.php Resource method → client delegation
└── Feature/
├── AuthenticationFlowTest.php Full auth + API call flows
└── ServiceProviderTest.php DI bindings, singleton, facade, config