Open source PAY by square, INVOICE by square and INVOICE ITEMS by square encoder/decoder written in Rust.
bysqr is a pre-1.0 project. Public Rust and WebAssembly APIs may change before
1.0. Encoding and decoding run locally on native targets and WebAssembly; no
external service is required.
Add the Rust library to an application with:
cargo add bysqrThe optional qr-reader feature adds QR extraction from PNG and JPEG images:
cargo add bysqr --features qr-readerInstall the headless CLI directly from crates.io with:
cargo install bysqrDownload the bysqr CLI from the
Releases page. Prebuilt native
binaries are available for macOS, Linux and Windows on x86_64 and AArch64.
Each native target has a standard build with the QR preview window and a smaller headless build for command-line and embedded use. Releases also include a WebAssembly package.
The CLI encodes and decodes PAY by square, INVOICE by square and INVOICE ITEMS by square. It supports PAY payment orders, standing orders and direct debits, and all five INVOICE document types.
To encode a PAY, INVOICE or one INVOICE ITEMS block to a QR code, run encode
with the source document:
bysqr encode --src payment.xml --save ~/Desktop/qr.svg
bysqr encode --src invoice.json --save ~/Desktop/invoice.svg
bysqr encode --src invoice-items.json --save ~/Desktop/items.svg
bysqr encode --src '<?xml version="1.0"?><Pay type="Pay">...</Pay>' --save ~/Desktop/qr.svg--src accepts a PAY, INVOICE or INVOICE ITEMS JSON/XML document, either inline
or as a file path. The document data selects the encoder and QR branding.
Pass --src - to read textual source data from standard input. This transport
is available for encode, decode, encode-items and decode-items.
The canonical JSON structure mirrors spec/bysquare.xsd: element names remain
in PascalCase and XML collections remain explicit objects, such as
Payments.Payment and BankAccounts.BankAccount.
{
"Payments": {
"Payment": [
{
"PaymentOptions": "paymentorder",
"Amount": "12.34",
"CurrencyCode": "EUR",
"BankAccounts": {
"BankAccount": [
{ "IBAN": "SK7700000000000000000000" }
]
}
}
]
}
}bysqr encode --src payment.json --save ~/Desktop/qr.svgThe JSON Schema Draft 2020-12 files define each format:
- PAY
- INVOICE, including all five
DocumentTypevalues - one INVOICE ITEMS QR block
- a complete ordered InvoiceItemsList
Schema-conformant decimal values are strings to preserve exact precision;
numeric JSON input is also accepted. VAT rates use the range 0 through 1,
so 20% is "0.2". An InvoiceItemsList has no FirstInvoiceLineID because
chunking assigns that value to each encoded QR block.
Computed XSD properties are optional read-only values in the JSON Schema and
are not transported in the QR sequence. Use Invoice::calculate_totals and
invoice_items::InvoiceLine::calculate when computed values are needed.
bsqr:maxLength annotations are exposed as advisory diagnostics and never
cause silent truncation; hard XSD constraints and the applicable QR transport
limits are enforced.
Use --save to write an image. The file extension selects SVG, PNG or JPEG.
PAY and INVOICE support the fixed presets defined by the by-square logo manual: print or electronic layout, branding at the bottom, top, left or right, and light, dark, gray or black color variations. Light and dark use blue for PAY and orange for INVOICE. Both families default to the dark print layout with bottom branding.
bysqr encode --src invoice.json --save invoice.svg \
--logo-layout electronic \
--logo-position left \
--logo-color grayCustom colors and logo proportions are not supported. The QR matrix remains black on white, and INVOICE ITEMS uses its fixed black composition.
Use --preview to display the generated QR code in a window instead of saving
it.
bysqr encode --src payment.xml --previewThis option is not available in headless builds.
Use --format instead of --save to write the image to standard output. SVG
is emitted as XML; PNG and JPEG are emitted as Base64 data URLs.
bysqr encode --src payment.xml --format svg # output: <svg xmlns="http://www.w3.org/2000/svg">...</svg>
bysqr encode --src payment.xml --format png # output: data:image/png;base64,...
bysqr encode --src payment.xml --format jpeg # output: data:image/jpeg;base64,...For PNG and JPEG, --size sets the image width in pixels. Height is calculated
from the selected QR composition. SVG output ignores this option.
# Create a PNG image 1024 pixels wide.
bysqr encode --src payment.xml --format png --size 1024For JPEG, --quality accepts a value from 1 to 100 and defaults to 90.
bysqr encode --src payment.xml --format jpeg --quality 95encode-items accepts one complete InvoiceItemsList JSON/XML document and
splits it into the specification's recommended four-line QR blocks.
# stdout is a JSON array containing one SVG string per QR block
bysqr encode-items --src invoice-items-list.json --format svg
# write invoice-items-001.png, invoice-items-002.png, ...
bysqr encode-items --src invoice-items-list.json --format png --save items-qr
# stdin is useful for process wrappers
cat invoice-items-list.json | bysqr encode-items --src - --format jpegWithout --save, SVG output is a JSON array of SVG strings and PNG/JPEG output
is a JSON array of Base64 data URLs. With --save, the destination is a
directory. Existing generated files are rejected unless --overwrite is
provided.
decode-items accepts a JSON array containing the textual contents scanned
from all related QR codes. Block order does not matter; gaps, overlaps and
mixed InvoiceID values are rejected. The result is one InvoiceItemsList
JSON/XML document.
bysqr decode-items --src scanned-payloads.json --format json
cat scanned-payloads.json | bysqr decode-items --src - --format xmlBoth batch commands accept an optional --invoice-src invoice.json argument to
validate the aggregate InvoiceID and item count against the parent INVOICE.
The decoder accepts Base32hex QR content and prints a PAY, INVOICE or INVOICE
ITEMS document as JSON or XML. JSON output conforms to the corresponding schema
in spec/.
bysqr decode --src '000620000...' --format json
bysqr decode --src payload.txt --format xmlRaster image reading is optional so applications that already use their own QR
scanner do not need to compile another one. Enable it with the qr-reader
feature:
cargo build --release --features qr-reader
bysqr decode --src payment.png --format json
bysqr decode --src invoice.jpg --format xmlThe lower-level qr_reader::extract_payloads_from_bytes API returns the text
from every detected QR code. qr_reader::decode_document_from_bytes selects
and validates exactly one supported by-square document;
qr_reader::decode_pay_from_bytes remains available for PAY-only consumers.
qr_reader::decode_invoice_items_from_bytes reassembles every compatible Items
block found in one image.
Install the latest Rust, then build the project with Cargo:
cargo build --releaseCargo writes the bysqr executable and Rust library to target/release.
Both deserialization and encoding return typed errors:
use bysqr::pay;
let pay = pay::try_deserialize_pay(include_str!("payment.xml"))?;
let payload = pay::encode(&pay)?;
let decoded = pay::decode(&payload)?;
assert_eq!(decoded, pay);
# Ok::<(), bysqr::error::Error>(())INVOICE uses the parallel domain API:
use bysqr::{invoice, Document};
let invoice = invoice::try_deserialize_invoice(include_str!("invoice.json"))?;
let payload = invoice::encode(&invoice)?;
let decoded = invoice::decode(&payload)?;
assert_eq!(decoded, invoice);
assert!(matches!(bysqr::decode(&payload)?, Document::Invoice(_)));
# Ok::<(), bysqr::error::Error>(())PAY and INVOICE rendering uses the same fixed LogoTheme presets. The renderer
selects the family-specific palette:
use bysqr::qr::{
create_invoice_svg_with_theme, create_pay_svg_with_theme, LogoColor,
LogoLayout, LogoPosition, LogoTheme,
};
let theme = LogoTheme::new(
LogoLayout::Electronic,
LogoPosition::Right,
LogoColor::Black,
);
let pay_svg = create_pay_svg_with_theme(&pay_payload, theme)?;
let invoice_svg = create_invoice_svg_with_theme(&invoice_payload, theme)?;
# Ok::<(), bysqr::error::Error>(())create_pay_svg, create_invoice_svg and create_invoice_items_svg use their
default compositions. SVG creation and the PNG/JPEG raster helpers return
bysqr::error::Result. Raster dimensions are limited to 8,192 pixels per side;
JPEG quality must be 1–100.
LogoLayout::ALL, LogoPosition::ALL and LogoColor::ALL expose the full
2 × 4 × 4 preset matrix. A deterministic visual gallery can be generated with:
cargo run --example theme_previewThe resulting target/theme-preview.html compares PAY and INVOICE across all
32 variants.
InvoiceItemsList represents a complete ordered item list. Chunking assigns
the block-local FirstInvoiceLineID automatically and uses the specification's
recommended four lines per QR. The decoder also accepts larger deployed blocks:
use bysqr::invoice_items::{self, InvoiceItemsList};
let list: InvoiceItemsList = serde_json::from_str(include_str!(
"invoice-items-list.json"
))?;
let payloads = list.encode_chunks()?;
let reassembled = invoice_items::decode_chunks(&payloads)?;
assert_eq!(reassembled, list);
# Ok::<(), Box<dyn std::error::Error>>(())When a parent invoice::Invoice is available,
reassembled.validate_against_invoice(&invoice) checks its InvoiceID and
NumberOfInvoiceLines.
Lower-level encode_sequence, decode_sequence and codec::decode_payload
APIs are available for conformance tooling. The embedded JSON_SCHEMA
constants expose the PAY, INVOICE and single-block INVOICE ITEMS schemas;
invoice_items::JSON_SCHEMA_LIST exposes the aggregate schema.
PAY and INVOICE encode and encode_sequence enforce the 550-character QR
limit. Non-QR integrations can use encode_with_limit with
SequenceLimit::Unbounded; the protocol-level 16-bit payload limit still
applies. The INVOICE ITEMS high-level encoder chunks the item list instead of
applying a global sequence limit.
With qr-reader, pass raster bytes to
qr_reader::decode_document_from_bytes. Otherwise, pass text from an external
scanner to bysqr::decode. bysqr::try_deserialize provides the corresponding
JSON/XML document classification.
Run the complete suite with:
cargo test --all-featuresThe Tests GitHub Actions workflow runs formatting, Clippy, the complete Rust
suite, crate package verification, and the WASM/Node boundary suite on every
push and pull request.
Maintainer release instructions, including the initial crates.io publication
and subsequent Trusted Publishing workflow, are in
docs/releasing.md.
Offline fixtures cover known payloads, XSD-derived cases and multi-QR INVOICE ITEMS. End-to-end tests also render and scan PAY, INVOICE and ITEMS images. They compare decoded data rather than compressed strings because equivalent LZMA streams need not be byte-identical.
The wasm feature runs the encoder, decoder and renderer in a browser without a
server.
The low-level API and its structured errors are documented in
docs/wasm.md.
Before building for wasm target, install the same pinned wasm-pack release used
by CI.
cargo install wasm-pack --version 0.15.0 --lockedBuild the web package and run the complete Node boundary suite with one command:
./scripts/test-wasm.shThe generated module is written to pkg.
Before building wasm on Ubuntu, make sure to install all necessary tools:
sudo apt install -y build-essential clangApple's system clang does not provide the wasm32-unknown-unknown target needed
to compile the bundled LZMA C library. Install Homebrew LLVM and make sure its
clang appears before /usr/bin/clang when running wasm-pack.
brew install llvm
# These can be placed in ~/.zshrc.
export PATH="$(brew --prefix llvm)/bin:$PATH"
export LDFLAGS="-L$(brew --prefix llvm)/lib"
export CPPFLAGS="-I$(brew --prefix llvm)/include"
# Must report Homebrew clang, not Apple clang from /usr/bin.
which clang
clang --version
wasm-pack build --target web --features wasm- Stabilize the public API based on 0.x integration feedback.
- Complete browser and npm integration in the JavaScript SDK.