Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pcc 3 Deklaracja Laravel Package

kch/pcc-3-deklaracja

Laravel/PHP package for generating and managing Polish PCC-3 tax declarations for civil law transactions. It simplifies preparing declaration data, calculating tax, and producing compliant documents, helping automate PCC-3 form creation and integrate declaration workflows into your application.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a plain Composer library: a set of DTO/domain classes annotated for jms/serializer that model the Polish PCC‑3 declaration. There is no service provider, no config file, no facade, no migrations — despite the "Laravel package" framing. Plan to wire the serializer yourself.

1. Install (and expect to fight Composer).

composer require kch/pcc-3-deklaracja

The last tagged release is October 2017, so it targets PHP 7.0/7.1 and an old jms/serializer. On a modern stack (PHP 8.2+, Laravel 10/11) the default composer require will usually fail on platform or dependency constraints. Two realistic options:

  • If you must install unmodified for a spike: composer require kch/pcc-3-deklaracja --ignore-platform-reqs into a throwaway project, then tighten constraints by hand.
  • For anything closer to production: fork the repo into internal VCS, bump PHP + jms/serializer:^3, tag it, and require that fork. The domain surface is small; the fork cost is measured in hours, not weeks.

2. Where to look first (in this order).

  1. src/ — locate the root declaration class (likely Deklaracja, DeklaracjaPCC3, or under Model/). Its JMS annotations (@XmlRoot, @XmlElement, @XmlAttribute, @SerializedName) are the contract — the README is not.
  2. tests/ — if present, these are worth more than the docs. A test that builds a declaration, serializes it, and asserts against a fixture XML is the fastest possible tour of the object graph.
  3. The official PCC‑3 XSD from the Ministry of Finance (bip.mf.gov.pl). Class names generally map 1:1 to element names; keep the schema open beside your editor rather than guessing setter names.

3. First use case — round-trip a single declaration.

Before touching controllers or Eloquent, get one declaration to serialize and validate against the XSD:

use JMS\Serializer\SerializerBuilder;
use Kch\Pcc3\Model\Deklaracja; // confirm namespace from src/

$serializer = SerializerBuilder::create()->build();

$deklaracja = new Deklaracja();
// populate: header (rok, kwartał/miesiąc, kod urzędu),
// podmiot (taxpayer), pozycje szczegółowe, przedmiot opodatkowania

$xml = $serializer->serialize($deklaracja, 'xml');

file_put_contents(storage_path("app/pcc3/{$transaction->id}.xml"), $xml);

Validate immediately — ideally in the same script:

$dom = new DOMDocument();
$dom->loadXML($xml);
libxml_use_internal_errors(true);
$valid = $dom->schemaValidate(storage_path('app/schemas/pcc-3.xsd'));

If that round-trip works and the XML is schema-valid, you have proven the integration is viable. If it fails on modern JMS, you have your signal to rebuild the DTOs from the XSD instead — the object graph is not large.


Implementation Patterns

Wrap it behind an internal interface. Never let controllers or models touch Kch\... classes directly. Treat the package (or your fork) as an isolated adapter.

namespace App\Compliance\Pcc3;

interface Pcc3Generator
{
    public function generate(Pcc3Declaration $declaration): string; // returns XML
}

final class JmsPcc3Generator implements Pcc3Generator
{
    public function __construct(private SerializerInterface $serializer) {}

    public function generate(Pcc3Declaration $d): string
    {
        return $this->serializer->serialize($d, 'xml');
    }
}

Bind in a dedicated service provider, keeping the JMS serializer confined to this class. This is your swap point if you migrate to a hand-written builder later.

$this->app->singleton(SerializerInterface::class, fn () =>
    SerializerBuilder::create()->build()
);
$this->app->bind(Pcc3Generator::class, JmsPcc3Generator::class);

Separate your domain DTO from the package DTO. Your application already has Loan, Transaction, Customer models. Don't pass Eloquent models into the serializer. Introduce a small mapping layer:

final class Pcc3Mapper
{
    public function fromLoan(Loan $loan): Pcc3Declaration
    {
        // map loan fields -> package Deklaracja / podmiot / pozycje
        // compute or read tax amount here, deliberately, not inside the package
    }
}

This keeps Eloquent changes from breaking the XML shape, and keeps the tax calculation testable in isolation.

Queue batch generation. For a portfolio of loans (the origin story of this repo — a payday lender), generate declarations as a job, not inline:

final class GeneratePcc3ForLoan implements ShouldQueue
{
    public function __construct(public int $loanId) {}

    public function handle(Pcc3Generator $gen, Pcc3Mapper $mapper): void
    {
        $loan = Loan::findOrFail($this->loanId);
        $xml  = $gen->generate($mapper->fromLoan($loan));

        Storage::disk('pcc3')->put("{$loan->id}.xml", $xml);
        // record declaration metadata (schema version, generated_at, checksum)
    }
}

Persist alongside the XML: schema version, generator version (fork tag), timestamp, and a checksum. Compliance artifacts without provenance are hard to defend in an audit.

Treat generated XML as an immutable, versioned artifact. Store it on a disk dedicated to compliance, reference it from the transaction, and never regenerate in place. Write a new version. This makes the "what did we actually file?" question trivially answerable.

Gate submission behind an explicit, separate step. This package produces a document; it does not submit to e‑deklaracje. Keep generation and submission as distinct application services so a change to the filing channel doesn't touch declaration assembly.

Testing pattern: golden files + XSD.

public function test_pcc3_for_personal_loan_is_schema_valid(): void
{
    $xml = app(Pcc3Generator::class)->generate(
        (new Pcc3Mapper())->fromLoan(Loan::factory()->personalLoan()->create())
    );

    $this->assertXmlMatchesSchema($xml, resource_path('schemas/pcc-3.xsd'));
    $this->assertSame(
        file_get_contents(base_path('tests/fixtures/pcc3/personal-loan.xml')),
        $xml
    );
}

Golden files catch silent serialization drift after a jms/serializer bump. XSD validation catches structural breaks after a schema update. You want both.


Gotchas and Tips

The package is abandoned — own it immediately. One star, zero dependents, last release 2017. There is no upstream support, no security patching, and no community knowledge base. Fork it, pin the fork tag, and assign a named internal owner. Do not add the upstream package as a runtime dependency in a customer-facing service.

JMS annotation syntax has moved on. The code uses @Serializer\... docblock annotations via a Doctrine annotation reader. Modern jms/serializer (3.x) prefers PHP attributes or explicit @XmlRoot/metadata configuration, and Doctrine annotations are deprecated or removed depending on the version. Expect to convert annotations to attributes during the fork. Budget this as the single largest migration cost.

Do not trust the tax computation — or trust it only after legal review. Verify whether the package merely assembles declared amounts or actually computes podatek. If it computes, do not treat the result as authoritative; PCC tax rules and rates have changed since 2017. Own the calculation in your mapper and test it against known official examples.

Schema drift is your highest-impact failure. The 2017 release emits some version of the PCC‑3 schema. The current e‑deklaracje specification may differ — field names, optionality, namespaces, encodings. Always validate against the current official XSD in CI, and pin the XSD version you validate against. A generated XML that is well-formed but schema-stale will be rejected at submission time, often late.

Encoding and Polish formatting. e‑deklaracje has historically been picky about encoding and decimal separators. Verify the exact encoding the current portal expects and confirm what the serializer emits. If the package outputs UTF‑8 and the portal wants something else (or vice versa), handle it in a post-processing step, not by editing the package's annotations. Decimal amounts (grosze) are a classic source of rejection.

Null vs. empty elements. JMS serializes unset properties differently depending on configuration. A required element that serializes as <element/> versus being omitted entirely are two different rejection modes. Inspect the raw output for required fields before assuming the serializer "did the right thing."

Don't share serializer state across requests. JMS serializers are cheap to construct and should not be reused across long-lived contexts (Octane, queue workers) without care. A per-request or per-job instance avoids stale metadata caches and gives you a clean failure surface.

Debugging serialization. When output looks wrong, add a debug serializer and inspect the metadata:

$serializer = SerializerBuilder::create()->setDebug(true)->build();

setDebug helps surface mapping problems via exceptions rather than silently dropping fields. SerializationContext with SerializationContext::create()->enableMaxDepthChecks() is also useful for tracking down missing nested values.

Extension points, in order of preference:

  1. Your mapper — always the first lever. Add fields, normalize values, compute totals here; never upstream in vendor/.
  2. A custom JMS handler — if a value needs a bespoke serialization format (dates, amounts), register a handler on your serializer rather than editing annotations.
  3. Forked annotations/attributes — for schema changes, fork, adjust the DTO, add golden-file test, bump version.
  4. A new DTO — if the package's object graph can't express what the current XSD requires, write a small hand-rolled builder producing DOMDocument. Given how small the PCC‑3 surface is, this is often the cleanest end state and worth comparing against fork effort.

CI guardrail. Add a pipeline step that (a) validates generated XML against the pinned XSD and (b) diffs against golden fixtures. Without this, a routine Composer update is a compliance regression waiting to be discovered by the tax office.

Keep the schema XSD in the repo. Vendor it at resources/schemas/pcc-3.xsd with a version comment. When the Ministry updates it, you want a one-line diff, not an archaeology expedition.

Ramp-up expectation. A mid-level Laravel developer unfamiliar with JMS Serializer will need roughly a day to understand the object graph and serializer configuration. The larger cost is domain learning — Polish PCC rules, required fields, and submission requirements — which typically needs a tax/legal reviewer in the loop. Factor that into your estimate rather than assuming the package shortens the timeline as much as it shortens the code leg.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin