Search by

flairuk / laravel-square

ijeffro

Square payments for Laravel: the official Square PHP SDK, configured, plus webhooks, idempotency keys, money helpers, OAuth and a Web Payments card form.

Package info

github.com/FLAIRUK/laravel-square

pkg:composer/flairuk/laravel-square

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-10-05 11:24 UTC

This package is auto-updated.

Last update: 2026-10-05 11:24:22 UTC


README

Square for Laravel

PHP 8.2+  Laravel 12 or 13  Lint  Tests  Downloads on Packagist  MIT licence  Square API 
 

Square for Laravel — The official Square PHP SDK, configured for Laravel 12 and 13, with the parts the SDK leaves to you.

  • The real SDK, configured. Square::payments(), Square::customers() and every other Square API, set up from .env for sandbox or production. Nothing is re-implemented, so new Square APIs arrive with SDK updates.
  • Testable. The SDK sends its requests through Laravel's HTTP client, so Http::fake() works in your tests.
  • Webhooks. Signature-verifying middleware and an opt-in route that turns each notification into a Laravel event, with duplicates filtered out.
  • Safe payments. Idempotency keys, including deterministic ones, so a retried job never charges twice. Money helpers convert "12.50" to minor units without floating-point errors.
  • Multi-merchant. An OAuth helper (code and PKCE flows) and Square::forMerchant($token).
  • Card form. A Blade component for the Web Payments SDK card field.

📦 Installation · 🚀 Usage · 🔔 Webhooks · 💳 Card form · 🔑 OAuth · 🔌 Testing your integration



📦 Installation

composer require flairuk/laravel-square
php artisan square:install

Requires PHP 8.2 or later with Laravel 12, or PHP 8.3 or later with Laravel 13.

square:install publishes config/square.php and adds any of these keys that are missing to .env and .env.example, empty. Fill them in from the Developer Console:

SQUARE_ACCESS_TOKEN=EAAA...
SQUARE_ENVIRONMENT=sandbox          # or production
SQUARE_LOCATION_ID=L...             # default location
SQUARE_APPLICATION_ID=sandbox-sq0idb-...
SQUARE_WEBHOOK_SIGNATURE_KEY=       # only for webhooks
SQUARE_WEBHOOK_URL=                 # only for webhooks

Then check the connection. It lists the locations the token can access:

php artisan square:status

Optional settings: SQUARE_VERSION pins the Square-Version header (by default, the version the installed SDK was built for), SQUARE_CURRENCY sets the default currency for Square::money() (default USD), and SQUARE_APPLICATION_SECRET and SQUARE_OAUTH_REDIRECT_URI are used by OAuth.

The SDK's major version changes often, as Square releases new API versions. This package supports square/square 45 to 47. The SDK retries connection errors, 408, 429 and 5xx responses itself, so always send an idempotency key with writes.



🚀 Usage

use FLAIRUK\Square\Facades\Square;

You can also type-hint FLAIRUK\Square\Square, or Square\SquareClient for the SDK client itself. Both are singletons.

Taking a payment

use Square\Payments\Requests\CreatePaymentRequest;

$response = Square::payments()->create(new CreatePaymentRequest([
    'sourceId' => $request->input('source_id'),         // the token from the card form
    'idempotencyKey' => Square::idempotencyKey('order', $order->id),
    'amountMoney' => Square::money('12.50', 'GBP'),     // 1250 pence
    'locationId' => Square::locationId(),
]));

$response->getPayment()->getId();
$response->getPayment()->getStatus();   // "COMPLETED"

Every Square API

Each API client on the SDK's SquareClient is a method on the facade, returning that client:

use Square\Customers\Requests\CreateCustomerRequest;
use Square\Refunds\Requests\RefundPaymentRequest;

Square::locations()->list()->getLocations();

Square::customers()->create(new CreateCustomerRequest([
    'idempotencyKey' => Square::idempotencyKey(),
    'givenName' => 'Jane',
    'emailAddress' => 'jane@example.com',
]));

foreach (Square::customers()->list() as $customer) {   // pages are fetched as you go
    // ...
}

Square::refunds()->refundPayment(new RefundPaymentRequest([
    'idempotencyKey' => Square::idempotencyKey('refund', $payment->id),
    'amountMoney' => Square::money('5.00', 'GBP'),
    'paymentId' => $payment->id,
]));

Square::client();   // the Square\SquareClient

orders(), catalog(), inventory(), invoices(), subscriptions(), giftCards(), loyalty(), bookings(), terminal(), teamMembers() and the rest work the same way. See the SDK reference for their methods. The SDK's own OAuth client is Square::client()->oAuth, because Square::oauth() is this package's OAuth helper.

Idempotency keys

Square::idempotencyKey();                         // a random UUID
Square::idempotencyKey('order', 42, 'payment');   // the same key every time for these parts

Square returns the original result when it sees a key again, so a deterministic key makes a retried job or a double-clicked button safe. Keys are 36 characters, within Square's 45-character limit. The same methods are on FLAIRUK\Square\Support\IdempotencyKey as generate() and for(...).

Money

Square amounts are integers in the currency's smallest unit: pence, cents, or whole yen.

use FLAIRUK\Square\Support\Money;

Square::money('12.50');            // Square\Types\Money: 1250 in SQUARE_CURRENCY
Square::money(1500, 'JPY');        // 1500 yen (JPY has no minor unit)

Money::toMinor('19.99', 'USD');    // 1999
Money::fromMinor(1999, 'USD');     // "19.99"
Money::toDecimal($payment->getAmountMoney());   // "12.50"
Money::ofMinor(1250, 'GBP');       // a Square\Types\Money from minor units
Money::decimals('KWD');            // 3

Amounts are converted as strings, so "19.99" is always 1999. An amount with more decimals than the currency allows, such as "1.234" GBP, throws an InvalidArgumentException instead of being rounded.

Other sellers

Square::forMerchant($seller->square_access_token)->payments()->list();

Errors

API errors are the SDK's own exceptions:

use Square\Exceptions\SquareApiException;
use Square\Exceptions\SquareException;

try {
    Square::payments()->create($request);
} catch (SquareApiException $e) {
    $e->getStatusCode();                // 400, 401, 402 ...
    $e->getErrors()[0]->getCode();      // "CARD_DECLINED", "UNAUTHORIZED" ...
    $e->getErrors()[0]->getDetail();
} catch (SquareException $e) {
    // Square could not be reached, or the response could not be read
}

This package's exceptions extend FLAIRUK\Square\Exceptions\SquareException. ConfigurationException means a setting is missing, such as SQUARE_ACCESS_TOKEN.



🔔 Webhooks

Square signs each notification with an HMAC-SHA256 of the notification URL followed by the raw body. Add a subscription in the Developer Console, then set:

SQUARE_WEBHOOK_SIGNATURE_KEY=...                        # from the subscription
SQUARE_WEBHOOK_URL=https://example.com/square/webhook   # exactly as entered in the subscription
SQUARE_WEBHOOK_PATH=square/webhook                      # registers the package's route

The route (POST /square/webhook, named square.webhook) has no session or CSRF middleware. It verifies the signature, answers 403 if it is missing or wrong, and dispatches two events for each notification:

use FLAIRUK\Square\Events\WebhookReceived;
use FLAIRUK\Square\Webhooks\WebhookEvent;
use Illuminate\Support\Facades\Event;

// Every notification
Event::listen(function (WebhookReceived $received) {
    $received->event->type;          // "payment.updated"
});

// One type, as "square.{type}"
Event::listen('square.payment.updated', function (WebhookEvent $event) {
    $event->eventId;                     // Square's event_id
    $event->merchantId;
    $event->createdAt;
    $event->objectId();                  // the payment ID
    $event->object('status');            // "COMPLETED"
    $event->object('amount_money.amount');
    $event->data;                        // the notification's "data"
    $event->payload;                     // the whole notification
    $event->toSdkEvent();                // Square\Types\PaymentUpdatedEvent, if the SDK has the class
});

Square can send a notification more than once. The route remembers each event_id for 48 hours in your default cache (set SQUARE_CACHE_STORE to use another), and acknowledges repeats without dispatching again. If a listener throws, the event is not remembered, so Square's retry is processed. Turn this off with square.webhooks.deduplicate. Square expects a fast 2xx, so do slow work in a queued listener.

To handle webhooks in your own controller, use the middleware instead:

Route::post('hooks/square', SquareWebhookController::class)->middleware('square.webhook');

The middleware checks the signature against SQUARE_WEBHOOK_URL. If that isn't set, it uses the URL the request arrived at, which may not match behind a proxy or load balancer. You can also check a signature yourself:

Square::webhookSignature()->verify($request->getContent(), $request->header('x-square-hmacsha256-signature'));



💳 Card form

<x-square::card-form /> renders the Web Payments SDK card field for SQUARE_ENVIRONMENT, using SQUARE_APPLICATION_ID and SQUARE_LOCATION_ID. Put it inside your form:

<form method="POST" action="{{ route('checkout.pay') }}">
    @csrf
    <x-square::card-form />
    <button type="submit">Pay £12.50</button>
</form>

When the form is submitted, the component tokenizes the card, puts the token in a hidden source_id input and submits the form. Card errors are shown under the field. Use the token as sourceId when taking the payment.

Every attribute is optional:

<x-square::card-form
    name="source_id"
    id="square-card"
    application-id="sandbox-sq0idb-..."
    location-id="L..."
    :verification="['amount' => '12.50', 'currencyCode' => 'GBP', 'intent' => 'CHARGE', 'customerInitiated' => true, 'sellerKeyedIn' => false]"
    class="mb-4"
/>

verification is passed to card.tokenize() as-is, for buyer verification (SCA). If your site has a Content Security Policy, allow Square's script and frame hosts.



🔑 OAuth

To act for other sellers, set SQUARE_APPLICATION_ID, SQUARE_APPLICATION_SECRET and SQUARE_OAUTH_REDIRECT_URI (the redirect URL on your application's OAuth page), then:

// Redirect the seller to Square
$state = Str::random(40);
session(['square_state' => $state]);

return redirect(Square::oauth()->authorizeUrl(['MERCHANT_PROFILE_READ', 'PAYMENTS_WRITE'], $state));

// In the callback
abort_unless(hash_equals(session()->pull('square_state', ''), (string) $request->query('state')), 403);

$token = Square::oauth()->exchangeCode($request->query('code'));
$token->getAccessToken();
$token->getRefreshToken();
$token->getMerchantId();
$token->getExpiresAt();   // access tokens last 30 days: refresh them before then

Square::oauth()->refresh($refreshToken);
Square::oauth()->revoke(merchantId: $merchantId);

For the PKCE flow (no application secret, e.g. for a mobile or single-page app's back end):

use FLAIRUK\Square\OAuth;

['verifier' => $verifier, 'challenge' => $challenge] = OAuth::pkce();

Square::oauth()->authorizeUrl($scopes, $state, codeChallenge: $challenge);
Square::oauth()->exchangeCode($code, codeVerifier: $verifier);
Square::oauth()->refresh($refreshToken, pkce: true);

Store tokens encrypted (for example with the encrypted cast), and use them with Square::forMerchant($accessToken).



🔌 Testing your integration

The SDK sends its requests through Laravel's HTTP client, so Http::fake() works in your own tests:

Http::fake([
    'connect.squareupsandbox.com/v2/payments' => Http::response([
        'payment' => ['id' => 'P1', 'status' => 'COMPLETED'],
    ]),
]);

To test a webhook listener, set SQUARE_WEBHOOK_SIGNATURE_KEY and SQUARE_WEBHOOK_URL for your tests (in phpunit.xml or .env.testing) and sign the body with them:

$body = json_encode(['type' => 'payment.updated', 'event_id' => 'evt-1', 'data' => [...]]);

$this->call('POST', '/square/webhook', server: [
    'HTTP_X_SQUARE_HMACSHA256_SIGNATURE' => Square::webhookSignature()->sign($body),
], content: $body)->assertOk();

In Square's sandbox, use the test card numbers, or the cnon:card-nonce-ok source ID to skip the card form.



🧪 Testing

composer test



🔒 Security

If you discover a security issue, please email ijeffrouk@gmail.com instead of using the issue tracker.



🙌 Credits



📄 License

MIT. See LICENSE.