rajtik76 / payment-qr-code-php
PHP library for generating SPD and SCD payment QR codes (SPAYD format) for Czech domestic payments.
Requires
- php: >=8.4
- ext-ctype: *
- ext-gd: *
- endroid/qr-code: ^6.1
Requires (Dev)
- phpunit/phpunit: ^12.0
This package is auto-updated.
Last update: 2026-04-18 16:58:26 UTC
README
A PHP library for generating SPAYD (Short Payment Descriptor) QR codes used in Czech domestic payments. Compliant with the Czech Banking Association standard v1.2 (effective 2022-01-01), covering payment orders (SPD), immediate payments, standing orders, and collection consents (SCD).
The library validates every input against the specification before producing output, so an invalid object simply cannot be constructed. The generated string is ready to be fed to any QR encoder. A built-in generateBase64() method renders a PNG directly via endroid/qr-code and returns it as a data URI.
Requirements
| Dependency | Version |
|---|---|
| PHP | ≥ 8.4 |
| ext-gd | any |
| endroid/qr-code | ^6.1 |
Installation
composer require rajtik76/payment-qr-code-php
Quick start
use Rajtik76\QrCodePhp\SpdQrCode; // Minimal – only the recipient account is required $qr = new SpdQrCode(acc: 'CZ3301000000000002970297'); echo $qr->generate(); // SPD*1.0*ACC:CZ3301000000000002970297*CC:CZK* // Cast to string produces the same result echo (string) $qr;
// Typical payment order $qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '555.55', msg: 'PRISPEVEK NA NADACI', rf: '7004139146', xVs: '0987654321', xSs: '1234567890', xKs: '0558', dt: '20210430', ); echo $qr->generate(); // SPD*1.0*ACC:CZ3301000000000002970297*AM:555.55*CC:CZK*DT:20210430*MSG:PRISPEVEK NA NADACI*RF:7004139146*X-KS:0558*X-SS:1234567890*X-VS:0987654321*
// Render as a PNG data URI (suitable for an <img src="…"> tag) $dataUri = $qr->generateBase64(); // data:image/png;base64,iVBORw0KGgo…
Constructor parameters
All parameters are passed as named arguments. Only $acc is required; everything else defaults to null or a sensible value.
Core fields
| Parameter | Type | SPAYD key | Description |
|---|---|---|---|
acc |
string |
ACC |
Required. Recipient IBAN, optionally followed by +BIC. E.g. CZ3301000000000002970297 or CZ3301000000000002970297+GIBACZPX. Max 46 chars. IBAN checksum (mod-97) is validated. |
headerType |
HeaderType |
header | HeaderType::SPD (payment order) or HeaderType::SCD (collection consent). Default SPD. |
altAcc |
?string |
ALT-ACC |
Alternative recipient accounts, comma-separated IBAN[+BIC] pairs. Max 93 chars. Each account is individually validated. |
am |
?string |
AM |
Amount. Non-negative decimal, at most 2 decimal places, range 0–9999999.99. Pass as a string: '555.55'. |
cc |
?string |
CC |
ISO 4217 currency. Only 'CZK' is currently permitted. Defaults to 'CZK'; pass null to omit the field. |
dt |
?string |
DT |
Due date in YYYYMMDD format. For standing orders: first payment date. |
dl |
?string |
DL |
End/expiry date in YYYYMMDD format. For standing orders: expiry. For SCD: collection end. |
msg |
?string |
MSG |
Message for the recipient or standing order name. Max 60 chars. |
rf |
?string |
RF |
Payment reference number (digits only). Max 16 digits. |
rn |
?string |
RN |
Recipient name. Max 35 chars. |
pt |
?string |
PT |
Payment type. Pass 'IP' to request an instant payment. Max 3 chars. |
frq |
?Frequency |
FRQ |
Recurring payment frequency. See Frequency enum. |
dh |
?bool |
DH |
Death handling for standing orders: false = continue (DH:0), true = stop (DH:1), null = omit. |
nt |
?NotificationType |
NT |
Notification channel. See NotificationType enum. |
nta |
?string |
NTA |
Notification address. Required when nt is set. Phone (+digits) for SMS, valid e-mail for Email. |
withCrc32 |
bool |
CRC32 |
When true, appends a CRC32 checksum field. Default false. |
Extended Czech fields (X- prefix)
| Parameter | Type | SPAYD key | Description |
|---|---|---|---|
xVs |
?string |
X-VS |
Variable symbol. Digits only, max 10. |
xSs |
?string |
X-SS |
Specific symbol. Digits only, max 10. |
xKs |
?string |
X-KS |
Constant symbol. Digits only, max 10. Leading zeros are preserved. |
xPer |
?int |
X-PER |
Retry period in days for failed payments. Integer 1–30. |
xId |
?string |
X-ID |
Payer's internal payment identifier. Max 20 chars. |
xUrl |
?string |
X-URL |
Custom URL. Max 140 chars. |
xSelf |
?string |
X-SELF |
Message for the payer's own records. Max 60 chars. |
Enums
HeaderType
use Rajtik76\QrCodePhp\Enum\HeaderType; HeaderType::SPD // Short Payment Descriptor – payment orders, instant payments HeaderType::SCD // Short Collection Descriptor – collection consents / direct debits
Frequency
use Rajtik76\QrCodePhp\Enum\Frequency; Frequency::Daily // 1D Frequency::Monthly // 1M Frequency::Quarterly // 3M Frequency::SemiAnnually // 6M Frequency::Annually // 1Y
NotificationType
use Rajtik76\QrCodePhp\Enum\NotificationType; NotificationType::SMS // NT:P – notify via SMS; NTA must be a phone number NotificationType::Email // NT:E – notify via e-mail; NTA must be a valid address
Generating a PNG image
generateBase64() wraps generate() with endroid/qr-code's PngWriter and returns a data:image/png;base64,… URI. Every parameter mirrors the QrCode constructor exactly; all defaults are identical.
use Endroid\QrCode\Color\Color; use Endroid\QrCode\ErrorCorrectionLevel; use Endroid\QrCode\RoundBlockSizeMode; $dataUri = $qr->generateBase64( errorCorrectionLevel: ErrorCorrectionLevel::Medium, size: 400, margin: 16, foregroundColor: new Color(0, 0, 128), // navy blue modules backgroundColor: new Color(255, 255, 255), );
| Parameter | Type | Default | Description |
|---|---|---|---|
encoding |
EncodingInterface |
UTF-8 |
Character encoding. |
errorCorrectionLevel |
ErrorCorrectionLevel |
Low |
Error recovery capacity (Low / Medium / Quartile / High). |
size |
int |
300 |
Target image dimension in pixels. |
margin |
int |
10 |
Border around the QR code in pixels. |
roundBlockSizeMode |
RoundBlockSizeMode |
Margin |
How fractional module sizes are rounded. |
foregroundColor |
ColorInterface |
black | QR module colour. |
backgroundColor |
ColorInterface |
white | Background colour. |
logo |
?LogoInterface |
null |
Optional logo embedded in the centre. |
label |
?LabelInterface |
null |
Optional text label below the QR code. |
Note on image dimensions. With the default
RoundBlockSizeMode::Margin, the output image is exactlysize + 2 × marginpixels on each side, regardless of QR content. At the defaults that is 320 × 320 px.
Usage examples
Standing order
use Rajtik76\QrCodePhp\Enum\Frequency; use Rajtik76\QrCodePhp\SpdQrCode; $qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '555.55', frq: Frequency::Monthly, dt: '20210430', dl: '20230430', dh: false, msg: 'PRAVIDELNY PRISPEVEK NA NADACI', ); // SPD*1.0*ACC:CZ3301000000000002970297*AM:555.55*CC:CZK*DH:0*DL:20230430*DT:20210430*FRQ:1M*MSG:PRAVIDELNY PRISPEVEK NA NADACI*
Instant payment (IP)
$qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '199.00', pt: 'IP', msg: 'RYCHLA PLATBA', );
Collection consent (SCD)
use Rajtik76\QrCodePhp\Enum\Frequency; use Rajtik76\QrCodePhp\Enum\HeaderType; $qr = new SpdQrCode( acc: 'CZ3301000000000002970297', headerType: HeaderType::SCD, am: '555.55', frq: Frequency::Monthly, dt: '20210430', dl: '20260430', dh: false, msg: 'PRAVIDELNY PRISPEVEK NA NADACI', );
With CRC32 checksum
$qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '100.00', withCrc32: true, ); // SPD*1.0*ACC:CZ3301000000000002970297*AM:100.00*CC:CZK*CRC32:XXXXXXXX*
Payment notification
use Rajtik76\QrCodePhp\Enum\NotificationType; $qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '250.00', nt: NotificationType::Email, nta: 'platba@example.cz', );
Recipient account with BIC
$qr = new SpdQrCode( acc: 'CZ3301000000000002970297+GIBACZPX', am: '1000.00', );
Validation
Every field is validated in the constructor against the SPAYD v1.2 rules. A Rajtik76\QrCodePhp\Exception\ValidationException (extends \InvalidArgumentException) is thrown immediately on construction if any value is out of range or malformed. There is no deferred validation; a successfully constructed SpdQrCode object is always in a valid state.
use Rajtik76\QrCodePhp\Exception\ValidationException; try { $qr = new SpdQrCode( acc: 'CZ3301000000000002970297', am: '10000000.00', // exceeds maximum ); } catch (ValidationException $e) { echo $e->getMessage(); // AM must not exceed 9999999.99. }
Output format
Fields are emitted in alphabetical key order, which is also the canonical order required by the CRC32 specification. The format is:
{HEADER}*{VERSION}*{KEY}:{VALUE}*{KEY}:{VALUE}*…
Literal * characters inside field values are automatically percent-encoded as %2A.
Running the tests
composer install ./vendor/bin/phpunit
The test suite covers all 22 constructor parameters, all validation rules, CRC32 correctness, field ordering, special-character encoding, and image generation (PNG dimensions, magic bytes, per-parameter visual differences). Image tests require the GD extension and are automatically skipped when it is unavailable.
Specification
License
MIT — see LICENSE.md.