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 stringQr::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');| Enum | Cases |
|---|---|
BySquareVersion | V1_0_0, V1_1_0, V1_2_0 |
Periodicity | Daily, Weekly, Biweekly, Monthly, Bimonthly, Quarterly, Semiannually, Annually |
Month | January … December (bit flags; Month::mask() / Month::fromMask()) |
DirectDebitScheme | Other, Sepa |
DirectDebitType | OneOff, 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 cryptoBy 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.