supplycart/xero
A Laravel integration for the Xero accounting API by Supplycart, providing a simple way to connect your application to Xero for syncing invoices, contacts, payments, and other accounting data through an expressive, framework-friendly interface.
What the release actually changes. 0.3.23 is a one-line behavioral fix: when a token refresh fails, the package now resets the connection's enabled status. Read as a TPM, this is not a cosmetic patch — it converts a transient auth failure into a persisted state transition. The package owns a connection lifecycle (enabled → disabled on refresh failure), which means it is a stateful integration/connector layer (OAuth-style token holders, per-connection credentials) rather than a stateless utility. That framing drives everything below.
Architecture fit. The package fits where the application needs a managed registry of external connections with credential refresh: marketplace/CRM/social connectors, tenant-scoped integrations, webhook subscriptions. It assumes Laravel's container, Eloquent-backed persistence, queue/scheduler for refresh, and config-driven credentials. Fit is good if (a) we already treat "connection" as a first-class domain object with its own table and lifecycle, and (b) failure of one connection is expected and must not cascade. Fit is poor if token refresh is currently inline in request paths with no persisted connection state — the new behavior would introduce a state machine we are not observing.
Behavioral risk introduced by 0.3.23. The change is correct in intent (don't keep hammering a dead credential) but it is a silent, sticky state change:
Technical risk summary.
| Risk | Severity | Why |
|---|---|---|
| Silent disablement with no observability hook | High | Turns auth blips into data-pipeline outages discovered by customers |
| No documented retry/backoff before disabling | High | Reduces resilience vs. prior behavior |
| Refresh-failure concurrency / lost-update | Medium–High | Multi-worker refresh is the common Laravel deployment shape |
Pre-1.0 semver (0.3.x) |
Medium | Patch releases may carry behavior changes like this one; no stability guarantee |
| Re-enable workflow undefined | Medium | Determines whether this is self-healing or ticket-generating |
Key questions for the maintainer (blocking before adoption).
enabled is reset, or does the first failure disable?withoutOverlapping, optimistic version check, or nothing?Stack fit. Requires Laravel (container, config, Eloquent, migrations) and a queue worker or scheduler for refresh. It presumes our app can host a connections table with credential columns — so encryption-at-rest, key rotation, and secret handling fall to us, not the package. If we run multi-tenant, confirm the package's connection scoping is tenant-aware; if not, we wrap it. This is a normal, low-friction fit for a Laravel monolith or a modular monolith with a Connectors/Integrations module.
Migration path. There is no prior assessment and presumably no prior adoption, so this is greenfield: treat 0.3.23 as the baseline rather than an upgrade.
Connection domain model / repository so a future swap or fork is contained. Do not let controllers call the package directly.enabled flips or wrap the refresh call in a decorator. This is non-negotiable and is the main piece of work the release note implies.0.3.23), adopt behind a feature flag, roll out one connector type → one tenant cohort → all tenants.Compatibility. Because 0.3.x is pre-1.0, treat every patch as potentially behavior-changing and read the diff, not just the changelog, before bumping. Lock the version in composer.json (0.3.23 exact, not ^0.3) and do version bumps as deliberate PRs with the failure-path test in CI. Verify it does not collide with our existing HTTP client configuration, retry middleware, or Http fakes in tests — token-refresh packages frequently assume they own the Guzzle stack.
Sequencing (roughly 3–4 weeks for one engineer, part-time).
| Week | Work |
|---|---|
| 1 | Spike, failure-path observation, maintainer questions, go/no-go |
| 2 | Domain wrapper, migrations, encryption of credential columns, feature flag |
| 3 | Observability: disable event/metric/alert, re-enable runbook and admin action |
| 4 | Staged rollout, load sanity check on refresh jobs, document |
Maintenance. Small surface area and low code volume, but a single-maintainer, pre-1.0 dependency with a security-adjacent role (it holds refresh tokens). Budget for: reading diffs on every bump, an internal wrapper that isolates us from breaking changes, and a quarterly review of whether to keep, fork, or replace. If the package stalls, a fork is feasible given the small surface — but only if step 2 above (wrapper) was done.
Support. The new behavior shifts support load from "integration randomly erroring" to "connection turned itself off and nobody knows why." With the alerting in place, this is a net win — the failure is now explicit and diagnosable. Without it, it is a regression in diagnosability. Expect tier-1 tickets along the lines of "customer's integration stopped syncing"; the runbook must answer: how do I see why it was disabled, and how do I re-enable it in under five minutes. Write that runbook as part of rollout, not after the first incident.
Scaling. Refresh is per-connection and typically scheduled, so cost scales linearly with connection count — fine, but watch the scheduler: if refresh runs in a single serial job, a slow provider makes refresh lag and effectively disables healthy connections through timeout. Shard by connection or run per-connection jobs with withoutOverlapping. Concurrent refresh of the same connection is both a scaling and a correctness concern (see risk table). Storage is trivial.
Failure modes to instrument and alert on.
| Failure | Detection | Response |
|---|---|---|
| Connection silently disabled | Alert on enabled transition |
Runbook: inspect error, re-auth or retry |
| Mass disablement (provider outage) | Rate-of-disable alert with threshold (e.g. >5 in 10 min) | Suppress auto-disable or bulk re-enable; escalate to provider status |
| Refresh storm / scheduler lag | Job latency and backlog metrics | Shard, raise worker count |
| Concurrent refresh race | Duplicate-refresh log, disable-after-success | Add lock; report upstream |
| Credential leakage | Secret scanning, encryption audit | Rotate keys, revoke tokens |
Ramp-up. One backend engineer with Laravel experience can be productive in a day; the conceptual load is OAuth refresh semantics and the connection state machine, not the package. Add half a day of TPM/on-call review of the runbook. Do not spread ownership — a single named owner for this dependency is the cheapest mitigation for pre-1.0 risk.
Verdict. Adopt 0.3.23 as the baseline, conditional on (a) empirical confirmation of the disable/re-enable and concurrency semantics during the week-1 spike, and (b) shipping our own observability around the disable transition before any production traffic. The release itself is a reasonable fix; the risk is entirely in whether the newly-explicit failure state is visible and recoverable in our environment.
How can I help you explore Laravel packages today?