NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
QR for Laravel

PAY by square

PayBySquare implements the by square specification 1.0.0, 1.1.0 and 1.2.0 (the default). A document holds one or more payments — the first is the preferred one — each with one or more bank accounts:

use Carbon\CarbonImmutable;
use RoundlyConsulting\Money\Money;
use RoundlyConsulting\Qr\Enums\BySquare\BySquareVersion;
use RoundlyConsulting\Qr\Payloads\Payments\BySquare\{BankAccount, Beneficiary, PayBySquare, Payment};

$account = new BankAccount('SK46 1100 0000 0029 4714 7960', 'TATRSKBX');
$beneficiary = new Beneficiary('Roundly Consulting s.r.o.', street: 'Tolstého 5', city: '811 06 Bratislava');

$document = new PayBySquare(
    payments: [Payment::order(
        amount: Money::ofMinor(12550, 'EUR'),
        accounts: [$account],
        beneficiary: $beneficiary,
        dueDate: CarbonImmutable::parse('2026-10-15'),
        variableSymbol: '20260042',
        constantSymbol: '0308',
        note: 'Faktúra 2026-0042',
    )],
    invoiceId: '2026-0042',
    version: BySquareVersion::V1_2_0,     // null → config payments.bysquare.version
    deburr: true,                         // null → config payments.bysquare.deburr
);

$svg = Qr::payBySquare($document)->size(220)->svg();

Encoding and decoding

$string = Qr::payBySquare($document)->payload()->toQrString();  // the string the code carries (config applied)
$standard = $document->encode();          // standard defaults (1.2.0, deburr on) for null fields — ignores config
$model = $document->serialize();          // the tab-separated data model
$same = PayBySquare::decode($string);     // parse a PAY by square string

Qr::payBySquare() fills a null version or deburr from payments.bysquare.*; $document->encode() (and toQrString()) reads no configuration and falls back to the standard’s defaults, so it equals the QR content only while the configuration is at its defaults or the document sets version/deburr itself.

The code is a pure base32hex string (prefix 00… for 1.0.0, 04… for 1.1.0, 08… for 1.2.0) wrapping a CRC-32-checked, LZMA-compressed data model — compressed by the package’s own LZMA implementation. It is always one alphanumeric QR segment without ECI; error correction defaults to M. A corrupt string throws BySquareDecodeException with a reason naming the failed stage.

Standing orders and direct debits

use RoundlyConsulting\Qr\Enums\BySquare\{DirectDebitScheme, DirectDebitType, Month, Periodicity};
use RoundlyConsulting\Qr\Payloads\Payments\BySquare\{DirectDebitDetails, StandingOrderDetails};

// Standing order
Payment::standingOrder(
    new StandingOrderDetails(Periodicity::Monthly, day: 15, months: [Month::January, Month::July], lastDate: CarbonImmutable::parse('2027-12-31')),
    Money::ofMinor(3000, 'EUR'), [$account], $beneficiary,
);

// Direct debit
Payment::directDebit(
    new DirectDebitDetails(scheme: DirectDebitScheme::Sepa, type: DirectDebitType::Recurrent, mandateId: 'M-2026-7', maxAmount: Money::ofMinor(5000, 'EUR')),
    Money::ofMinor(1999, 'EUR'), [$account], $beneficiary,
);

// Amount left to the payer
Payment::order(null, [$account], $beneficiary, currency: 'EUR');
EnumCases
BySquareVersionV1_0_0, V1_1_0, V1_2_0
PeriodicityDaily, Weekly, Biweekly, Monthly, Bimonthly, Quarterly, Semiannually, Annually
MonthJanuary … December (bit flags; Month::mask() / Month::fromMask())
DirectDebitSchemeOther, Sepa
DirectDebitTypeOneOff, Recurrent

Limits

  • Amount ≥ 0; ISO 4217 currencies only — the amount’s own, or currency: when the amount is null (then required).
  • Variable and specific symbol up to 10 digits, constant symbol up to 4 digits; originator’s reference ≤ 35; note ≤ 140; invoice ID ≤ 10.
  • At least one account per payment; mandate, creditor and contract IDs ≤ 35.
  • Standing-order day 1–7 for weekly/biweekly, 1–31 otherwise, none for daily; direct-debit maxAmount in the payment currency.
  • Beneficiary name 1–70, street and city ≤ 70.

Versions and diacritics

Version 1.2.0 requires a beneficiary name on every payment; 1.0.0 has no beneficiary block. With deburr on (the default) diacritics are stripped from the note and beneficiary fields and the limits are checked again afterwards. Dates are calendar dates — pass them in the timezone you mean.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.