jetimob/juno-sdk-php-laravel
Laravel integration for the Juno payment gateway, wrapping the Juno PHP SDK with a service provider, configuration, and facades to simplify creating charges, issuing boletos, processing credit card payments, and handling webhooks in Laravel applications.
Install via Composer (package is archived, so pin it):
composer require jetimob/juno-sdk-php-laravel
Because the repo is archived and last released in April 2021, expect the underlying jetimob/juno-sdk-php to also be stale. It will pull in Guzzle and Laravel 8-era support; verify it installs cleanly on your Laravel version before committing.
Publish the config:
php artisan vendor:publish --provider="Jetimob\Juno\JunoServiceProvider" --tag=config
This drops config/juno.php. Fill in client_id, client_secret, and sandbox (or environment) so the SDK points at Juno's sandbox host during development.
Register credentials in .env — never in config directly:
JUNO_CLIENT_ID=
JUNO_CLIENT_SECRET=
JUNO_SANDBOX=true
First useful call — creating a charge (cobrança). The facade exposes resource accessors, so a typical first smoke test:
use Jetimob\Juno\Facades\Juno;
$charge = Juno::charges()->create([
'charge' => [
'description' => 'Teste',
'amount' => 10.00,
'dueDate' => now()->addDays(3)->format('Y-m-d'),
'paymentTypes' => ['BOLETO'],
],
]);
Check the access token flow — the package authenticates via OAuth2 client credentials against Juno's /authorization-server/oauth/token. The first thing to inspect after install is whether tokens are being cached or re-fetched per request (see Gotchas).
Resource-oriented facade. The day-to-day API is Juno::charges(), Juno::payments(), Juno::transfers(), Juno::balance(), Juno::digitalAccounts(), Juno::credentials() and similar. Each returns a resource object with create(), find(), list(), cancel(), etc. Treat the facade as a thin controller and wrap it in your own service class if you call more than two endpoints:
class JunoChargeService {
public function createForOrder(Order $order): Charge {
return Juno::charges()->create([
'charge' => [
'description' => $order->reference,
'amount' => $order->total,
'dueDate' => $order->due_at->format('Y-m-d'),
'paymentTypes' => ['CREDIT_CARD', 'BOLETO'],
],
]);
}
}
Sandbox/production switch through config. Keep JUNO_SANDBOX as an env var and never hardcode the base URL. Because the package is old, double-check it actually reads sandbox from config — some early SDKs hardcoded the API host in the service provider. If so, override the binding in your own AppServiceProvider.
Payment capture as a separate step. Juno separates charge (the intent) from payment (the transaction). A common pattern is to create the charge, then on card tokenization call Juno::payments()->create($chargeId, [...]). Model this as two application services: CreateChargeAction and CapturePaymentAction, and persist Juno's id (charge ID) alongside the payment ID on your local Order/Invoice model.
Webhooks as your source of truth. Juno posts events (CHARGE_CREATED, PAYMENT_CONFIRMED, PAYMENT_FAILED, etc.) to a URL you register. Register a controller route that:
resourceUrl by fetching it back via Juno::notifications()->find($id) (the package exposes a notifications resource in most builds).juno_events table for reconciliation.Token reuse across a request. Bind the SDK client as a singleton in the container so at least the same OAuth token is reused within a request lifecycle:
$this->app->singleton(JunoClient::class, fn ($app) => new JunoClient(config('juno')));
Testing strategy. Because the package is archived, don't rely on its own test suite. Wrap the facade in an interface (PaymentGateway) and swap to a fake in your tests. For integration tests, use Juno's sandbox with a dedicated test account and seed a small dataset of chargings.
Archived status is the headline risk. Last release 2021-04-13 means: no PHP 8.2+ guarantees, Laravel 9/10/11 compatibility unverified, Guzzle major bumps unhandled, and no security patches. On any modern Laravel app, budget for either pinning an old Laravel line, forking the two packages (juno-sdk-php-laravel + juno-sdk-php), or dropping the wrapper and calling Juno's REST API directly with Http::withToken().
Token caching bug class. Older builds often fetch a fresh OAuth token on every API call, which burns rate limits and adds latency. Check JunoClient/Auth implementation. If it does, add a caching decorator or a custom token provider bound in the container.
Base URL drift. Verify whether sandbox vs production is switched in the service provider or only in config/juno.php. If the SDK hardcodes api.juno.com.br, mirror the endpoint list into a services.juno config block and use Http::macro calls as an escape hatch.
Date and amount formats. Juno expects dueDate as Y-m-d and amount as a float/decimal; the SDK does not coerce your Carbon instances. Always ->format('Y-m-d') and cast money to (float) or integer-cents depending on the resource. A silent 422 on a correctly-shaped payload is almost always a date format issue.
Errors are often generic. The SDK tends to surface a JunoException with the raw response body. Log the full body (not just getMessage()) and inspect $e->getResponse(). Juno's field-level validation errors live inside the JSON errors array and are easy to miss.
Charge vs Payment naming confusion. Many devs hit Juno::charges()->create() expecting a completed payment — that only creates a cobrança. Money only moves when a payment is created for it or when a boleto is paid. If your test charge "succeeds" but no money arrives, this is why.
Webhook idempotency. Juno can retry notifications. Always key your event handler on the notification/id field and reject duplicates before mutating order state. Store the raw payload.
No DI, only facade. The facade resolves the client from the container each call, so you can swap it — but the resources are usually new-ed inside the facade closure. If you need to mock, bind a fake JunoClient singleton rather than mocking the facade.
Extension points are limited. Practical escape hatches: (a) a custom service provider that $this->app->extend()s the client, (b) a decorator class implementing your own JunoGateway interface, (c) bypass the wrapper with Laravel's Http client for any endpoint the SDK doesn't cover (PIX, newer digital-account features). Given the repo is archived, option (c) becomes more attractive the longer you keep the dependency.
How can I help you explore Laravel packages today?