arturas88/finvalda-sdk
A PHP SDK for integrating Finvalda accounting and business management services into Laravel applications. It provides a structured client for API authentication, data retrieval, and common Finvalda operations, simplifying financial system integration.
Architecture fit. The package is a fluent, builder-oriented PHP SDK over Finvalda's document API, consumed from a Laravel application via a service provider/facade. v3.4.0 continues a healthy pattern: typed builder methods (additionalCostCodes(), ProductLine::additionalCost()) replacing raw Lithuanian field names (sPapIslaiduKodas1..4, dPapIsldSumaV{slot}/dPapIsldSumaL{slot}), with validation moved to build() time (arity ≤ 4, slot range 1–4, code length 10). This is the right direction: it converts spec knowledge into library-enforced constraints rather than caller discipline. The per-line pair-write (V and L together) is a genuine defect-class removal — half-populated pairs previously booked lines wrong with no error.
Integration feasibility. Additive and non-breaking. No existing method signatures change; setHeader() remains available for the raw path. New surface is threefold: header buckets, per-line allocation, and a new purchaseUpdate() envelope. The first two are low-risk insert paths. The third is new, isolated, and gated behind an explicit method — nothing forces adoption.
Technical risk. Concentrated almost entirely in the correction path:
KoregPirkDok deletes named detail lines and re-adds supplied ones, rebuilding the product's FIFO stock layer. It is not an edit. A timeout or retry can double-apply.4027 the day the goods are consumed by another operation. Staging environments seeded with fresh, unsold data will pass while production fails — the worst possible test-gap shape.4027 is a deletion-class code returned by an update call. Any existing error mapping keyed on the 5000–5005 correction family will misclassify it (likely as a retryable/unknown fault).save() deliberately omits assertNotSold(); callers must remember a round-trip-per-code check. Fail-closed on unresolvable history is correct, but the default path is unguarded.Stock::purchaseOpFor() matches on op_rusis_pav values Pirkimai/Pardavimai that are observed from a live instance and appear in no API document. Unrecognised kinds are silently ignored, and a failed call and "no purchase history" both return null — the caller cannot distinguish "safe to correct" from "lookup broke."UpdateOperationClass cases still require hand-built payloads, i.e. the drift risk the release set out to fix persists elsewhere.Key questions for the maintainer.
purchaseOpFor() distinguish lookup failure from no-history — an exception, a status field, or a Response variant behind an opt-in flag?Pirkimai/Pardavimai, or a plan to fail loudly on unrecognised kinds instead of ignoring them?KoregPirkDok where deletes succeed and re-adds fail — is Finvalda transactional server-side?UpdateOperationClass envelopes, and for a ServiceLine equivalent if the spec ever gains one?Recommendation: adopt the insert-path features and the read-only lookup now; treat purchaseUpdate() as an opt-in, finance-approved, non-retryable operation until questions 1–4 are answered.
Stack fit. Standard Composer install into a Laravel app; calls are synchronous external I/O and must sit outside DB transactions — a correction cannot be rolled back by our application if the remote call times out. Route corrections through queued single-consumer jobs, not web requests.
Migration path. Incremental and reversible at each step:
setHeader() raw-field bucket writes with additionalCostCodes(); keep a parity test asserting identical payloads for one release cycle.dPapIsldSuma* writes with additionalCost()/additionalCosts(); the pair invariant is the reason to do this first.Stock::purchaseOpFor() as a read-only pre-flight in reconciliation/reporting paths before wiring it into any write path — this validates the undocumented enum assumption against our own Finvalda instance at zero business risk.Compatibility. No breaking changes in the notes. Two constraints to respect: additionalCostCodes() throws at build() on ->short() documents, and it is absent from purchaseReturn() — both are spec-correct but will surface as runtime exceptions in code paths that previously accepted raw header fields. Audit any shared builder code that conditionally short-circuits purchases.
Sequencing. (1) Header buckets → (2) per-line allocation → (3) lookup in read-only mode → (4) corrections behind a config flag, with assertNotSold() mandatory and enforced by an internal wrapper rather than by convention → (5) hand-built payloads for the other six update classes, each with a conformance test against a sandbox. Do not let step 4 ship in the same release as steps 1–2; the blast radii are not comparable.
Rollout gate for corrections: a staging dataset containing goods already sold post-purchase, so 4027 is exercised before production sees it.
Maintenance. Public API surface grows by three feature areas we now own the upgrade path for. Pin the minor version and read changelogs before bumping — the value of this release is precisely the kind of spec nuance (slot binding, Tik PirkDokPrekeDetEil annotations) that can shift between Finvalda server versions. Add a conformance test that runs the builders against a sandbox and asserts 4027 on a sold item; unit tests cannot catch that class of regression.
Support. Expect two ticket families. First, "this worked yesterday" on corrections — the answer is business-state, not code, and requires a runbook that maps 4027 to goods already consumed rather than to a retry. Second, finance sign-off requests: a correction is destructive and rebuilds cost layers, so this needs a named approver, not a developer decision. Document that corrections require explicit human authorization.
Scaling. assertNotSold() costs one round trip per touched product code, so a correction touching n products costs O(n) external calls on top of the write. Batch corrections must be throttled, serialized, and scheduled off-peak; measure Finvalda's tolerance rather than assuming it. The additional-cost insert path adds no round trips and scales with existing purchase throughput. The stock lookup is one call per product — cache results per reconciliation run, but never cache across a correction decision.
Failure modes to instrument.
4027 mid-batch: partial application (deletes done, re-adds not) with unknown remote state — alert and require manual reconciliation.null from purchaseOpFor(): log as unknown, never as safe.op_rusis_pav value after a Finvalda upgrade: monitor for the lookup returning null on products that demonstrably have purchase history.Ramp-up. The insert path is a half-day for a developer already familiar with the SDK — the fluent API reads well and the example in the notes is directly reusable. The correction path needs genuine onboarding: FIFO stock-layer semantics, the insert-vs-update envelope distinction (sZurnalas/nNumeris wrapper, PirkDokHeadEil, Del* nodes), the field-level refusals (sKlientas unchangeable on purchase, no operation-date fields), and why UpdPrekeDetEil is not an escape hatch. Budget one to two days plus an internal write-up; the cost of skipping it is production data corruption that surfaces weeks later, triggered by an unrelated sales transaction.
How can I help you explore Laravel packages today?