Releases: EKTIRAS/billing-php
v0.3.0 — document items, richer filters, monthly stats, regenerate PDF
Additive release pairing with the server's API v1.1. Existing v0.2.x callers keep working unchanged.
New DTOs
LineItem— one entry per line on a documentMonthlyStats—months[],bySource{},totalsBySource{},grandTotal
New methods
$doc->items // line items on GET /documents/{id}
Billing::products()->list(includeInactive: true)
Billing::stats()->monthly(months: 12) // per-source monthly revenue chart
Billing::documents()->regeneratePdf($id) // re-render a stale PDFRicher documents()->list() filters
All non-breaking. Pass any subset to list():
| Filter | Match |
|---|---|
mark |
exact |
full_number |
partial |
customer_email |
partial |
customer_company |
partial |
source |
exact |
Server side (same commit series in ektir-billing)
GET /documents/{id}response now includes theitemsarrayGET /documentsgains the filters aboveGET /products?include_inactive=1GET /stats/monthlyPOST /documents/{id}/regenerate-pdfPOST /documentswithsend_email=true→ 422 (was silently honoured before; server now rejects the field entirely)
Upgrading
No code changes required for existing SDK users. You'll just see new properties on the DTOs and new methods to use when you need them.
See CHANGELOG.md and README §7, §8.1, §9, §10.1, §16 for details.
v0.2.1 — security + forward-compat fixes
Security
- PDF download no longer leaks the API Bearer token.
Client::stream()previously attachedAuthorization: Bearer <key>to the signed PDF URL request. Signed URLs carry their own authentication — leaking the API key to that host is unnecessary and a latent risk. Fixed.
Fixed
- Greek / Unicode filenames preserved in
downloadPdf(). Full numbers likeΑ/2026/00001now produceΑ_2026_00001.pdf, matching what the README always claimed. - Unknown enum values no longer crash
Document::fromArray. CatchableUnknownEnumValueException(subclass ofEktirBillingException) instead of PHP's nativeValueError. DocumentPdfReadyevent fires exactly once — on the absent→present PDF transition, not on every subsequent poll.DocumentTracker::pending()now returns aprevious_has_pdfflag.PendingDocument::toArray()throwsInvalidBuilderStateException(subclass ofEktirBillingException) instead of plain\LogicException.await()retries transient errors (RateLimitException,TimeoutException) with backoff instead of aborting.
Added
- New exceptions:
UnknownEnumValueException,InvalidBuilderStateException. - CHANGELOG.md (Keep a Changelog format).
- 5 new tests covering the fixes above (8 total, 22 assertions, all green).
Upgrading
Fully backward-compatible for existing callers. Only behavioural change to watch:
If you wrote a custom DocumentTracker, the pending() return shape is now {id, previous_status, previous_has_pdf}. Implementations that still return the 2-key shape will see DocumentPdfReady fire on the first post-upgrade poll (treated as absent→present); no-op thereafter. See README §11 for the new example.
v0.2.0 — integrator owns email delivery
BREAKING change
The billing server no longer sends email to end customers on the integrator's behalf — you deliver the PDF from your own Laravel app, from your own sending domain.
Removed
PendingDocument::sendEmail()is gone from the fluent builder.
Added
Billing::documents()->pdfUrl($id)— returns a freshly-signed 24h URL you can drop into an email body or share.Billing::documents()->pdfAttachment($id)— returns anIlluminate\Mail\Attachmentready to use inside your Mailable'sattachments(). Bytes are fetched lazily when Laravel renders the mail.
Why
send_email=true on the server was convenient but wrong for integrators:
- The "From" address was the billing infra's address, not the integrator's domain.
- Subject / body / branding weren't customisable per integrator.
- Deliverability (SPF, DKIM, DMARC) lived on someone else's server.
Now the integrator writes a normal Laravel Mailable, attaches the PDF (or links to it), and sends through their own stack.
Upgrade
Before:
Billing::documents()->build()
->receipt()
->forCustomer($customer)
->addItem($sku, 1, 10.00)
->payCard()
->sendEmail() // ← remove
->create();After:
$doc = Billing::documents()->build()->receipt()->forCustomer($customer)->addItem($sku, 1, 10.00)->payCard()->create();
$ready = Billing::documents()->awaitPdf($doc->id);
Mail::to($customer->email)->send(new \App\Mail\InvoiceIssuedMail($doc));
// inside InvoiceIssuedMail::attachments():
// return [Billing::documents()->pdfAttachment($this->doc->id, "{$this->doc->fullNumber}.pdf")];For async flows, listen for DocumentPdfReady and mail from the listener — see README §11.
See README §6.3 for the full 'attach or link' walkthrough.
v0.1.0 — initial release
Official Laravel SDK for the EKTIR Billing API.
Highlights
- Typed SDK wrapping all 9 REST endpoints — documents (issue / list / find / cancel), products CRUD, EU OSS stats
- Fluent
PendingDocumentbuilder:Billing::documents()->build()->receipt()->forCustomer(...)->addItem(...)->payCard()->create() await()/awaitPdf()helpers for the myDATA → PDF → email async pipeline- One-line PDF download to any Laravel disk:
Billing::documents()->downloadPdf($id, disk: 's3') - Webhook replacement: scheduled
ektir:poll-documentscommand +DocumentSubmitted/DocumentPdfReady/DocumentFailed/DocumentStateChangedLaravel events. Bind aDocumentTrackerandEvent::listen(...)them like webhooks. - Typed exceptions mirroring the API error envelope (
ValidationException,RateLimitException,AuthenticationException,NotFoundException,CancelForbiddenException,TimeoutException) withApiKey($key)for multi-tenant appsHttp::fake()-friendly — full test recipes in the README
Requirements
- PHP 8.2+
- Laravel 11 or 12
Install
{
"repositories": [
{ "type": "vcs", "url": "https://github.com/EKTIRAS/billing-php" }
],
"require": { "ektiras/billing-php": "^0.1" }
}See the README for the full integration guide — country-specific rules, awaiting PDFs, polling & events, error handling, and testing.