Download the PHP package goldnead/statamic-invoices without Composer

On this page you can find all versions of the php package goldnead/statamic-invoices. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package statamic-invoices

Statamic Invoices

An invoice for every payment: a number that is unique and continuous, VAT decided by the buyer's country, and a document that never changes.

Sits on goldnead/statamic-payments. It writes invoices; it does not take money, and it does not do bookkeeping.

What it is for

statamic-payments can take money in Germany. It cannot, on its own, be used in Germany to sell a digital product to a consumer, because that requires an invoice — with a gapless number, the right VAT rate, and the sender's details on it. This addon is that missing half.

Requirements

Installation

Then fill in who is sending the invoices, before the first one goes out:

A document missing the sender's details is not a valid invoice in Germany — and it cannot be corrected afterwards, only reversed and reissued.

The number

German law wants a series that is unique and continuous. Both of those are properties of concurrency, not of arithmetic: two checkouts finishing in the same millisecond both read the same maximum and both write the same number, and neither notices.

So the number comes from a counter row that is locked while it is incremented, inside the same transaction that writes the invoice. Two processes queue; nobody gets the same number; nothing is skipped. A write that fails takes its number back with it.

One series per brand. Two brands sharing a counter each end up with a series full of holes from their own point of view — and it is each brand that has to answer for its own numbering.

Which brand an invoice belongs to is read off the payment, never off the process that writes it. statamic-payments stamps brand_id on the row while the buyer is still there; a webhook, a console run and a follow-up charge have no brand in the environment, and asking the environment there gets the default brand's answer rather than none. A payment that carries no brand at all still gets its invoice — refusing a document to somebody who has paid would leave a hole in a series that has to be gapless — but it says so in the log.

Lists invoices whose brand is not their payment's, with number, expected brand and actual brand. It reports and changes nothing: the number came out of one brand's counter and was counted there, so what a wrong document needs is a credit note plus a new invoice in the right series, and that is a decision for a person. A non-zero exit code means there were findings.

Changing the format later renumbers nothing: the resolved series is stored on the counter, so an old invoice stays in the series it was issued in.

It does not change

An invoice is immutable once written, and that is enforced on the model rather than agreed by convention:

A correction is a second document. Invoices::creditNoteFor($payment) writes a credit note that takes the next number in the series, copies the original's figures, and points at what it reverses. The tax is copied rather than recalculated — the rate that applied is the rate that applied, and looking it up again a month later could produce a different one, at which point the two documents would not cancel out.

On a full refund, statamic-payments 1.10 fires PaymentRefunded and the credit note is written by itself. A partial refund is not: which lines came back is a question only a person can answer, and guessing it would put a wrong figure on a tax document.

Events

Event When
InvoiceIssued An invoice exists. Fired after the transaction, so a listener always finds the row.
CreditNoteIssued An invoice was reversed. Carries both documents, because a credit note read alone says nothing about what it undid.
InvoiceDelivered The invoice reached the buyer's mailbox, and at which address. An event rather than a column, because the row refuses every update once it exists.

Filing it and handing it to an accountant hang off those.

As webhooks, through the Webhook Manager

With goldnead/statamic-webhook-manager installed, the three events appear there as triggers ("Invoices: invoice issued" / "Rechnungen: Rechnung ausgestellt"). Nothing to switch on: offering a trigger sends nothing, data leaves only through an outbound webhook somebody creates. INVOICES_WEBHOOK_MANAGER=false (invoices.webhook_manager.enabled) hides them. Each is delivered in the brand of the document, not the brand that happens to be current (an invoice is written in a payment webhook, where none is). A document naming a brand that cannot be set is not delivered and logged, rather than sent through the current brand's hooks. A moment inside a database transaction goes out after the commit, never after a rollback.

Duplicates and order. event_id is the same every time the same moment is told again, so a receiver can drop the repeat; a delivery to the same address within the same minute counts as one. occurred_at is the document's own time (issued_at), not the time of sending. Order is not guaranteed: invoices.delivered can reach a receiver before invoices.issued (the mail goes out from a listener of the same event). Sort by occurred_at, deduplicate by event_id.

Every body has the shape the whole suite sends:

Trigger Besides the common keys
invoices.issued invoice
invoices.credit_note_issued credit_note (same shape as invoice), reverses (id, number)
invoices.delivered invoice, to (the address it was mailed to)

invoice: id, number, kind (invoice, credit_note), payment_id, reverses_invoice_id, issued_at, currency, net_cent, tax_cent, gross_cent, tax_zone, buyer_name, buyer_email, buyer_country, buyer_vat_id, items[] (product, name, quantity, unit_net_cent, discount_cent, net_cent, tax_rate_bp, tax_cent, gross_cent).

Never in a body: the postal address, the seller block, the record of the VAT id check (service, status, the authority's reference), meta, a link to the PDF. Money is always *_cent next to currency; times are ISO 8601. brand is null where brand-context cannot name one.

The PDF

The document is rendered from the same Blade template the preview shows. There is no second layout to keep in step, which is the one thing a printed invoice cannot afford.

The same invoice always yields the same bytes. It is read off the stored row and its items — never recalculated — and the two things a PDF engine normally stamps with the wall clock (creation date, document id) are derived from the invoice instead. So a copy fetched in nine years is the document the buyer already has, not a similar one. Change the tax rules, the seller, the price basis or the number format afterwards: an invoice already written does not move a byte.

The legal texts (tax.texts, tax.legal_bases) stay yours to change. They are resolved once, when the invoice is written, and frozen onto it as prose — so an edit today changes what tomorrow's invoices say and nothing about yesterday's.

The engine is bound to an interface, not hard-wired. A host that already runs a headless browser, or has a print house with a template of its own, rebinds it:

Sending it to the buyer

On InvoiceIssued, so exactly the invoices that were written get sent, once each — no schedule, and no second place that decides whether a document should exist. A payment that is missing a mandatory detail still produces no invoice at all, and invoices:pending says which detail; the sending path cannot reach around that, because it only ever receives an invoice somebody else wrote.

The mail leaves through brand-context's BrandMailer, which decides who it comes from:

php artisan vendor:publish --tag=invoices-views publishes the covering letter alongside the document itself.

Writing the mail in the Control Panel (optional)

With goldnead/statamic-email-templates installed, the invoice mail registers there as invoices-invoice (config delivery.template, env INVOICES_MAIL_TEMPLATE), listed under "Invoices" with its occasion (InvoiceIssued) and placeholders: {{ buyer.name }} (the address when there is no name), {{ buyer.email }}, {{ invoice.number }}, {{ invoice.date }}, {{ amount }}, {{ seller.name }}, {{ site_name }}. An import (php please email-templates:import) writes the shipped wording as an entry.

Once an entry with that slug exists, it writes subject and text, in the layout email-templates wraps it in; the PDF is attached as before and the sender rules above still apply. Without an entry, or without the addon, the built-in mail goes out unchanged. delivery.template set to null or '' turns the template off.

Selling to businesses

A seller who only sells to businesses has three cases, not twenty-seven. A supply to a business abroad is taxed where the buyer sits (§ 3a Abs. 2 UStG), so no German VAT is charged in any of them and what differs is the sentence on the document:

Zone Buyer On the invoice
de domestic no VAT, § 19 UStG (or your ordinary rate)
eu-b2b EU business with a confirmed VAT ID no VAT, both VAT IDs, "Steuerschuldnerschaft des Leistungsempfängers" plus the English line (§ 14a Abs. 1 UStG)
third-country-b2b business outside the EU no VAT, "Leistung im Inland nicht steuerbar" / "Not taxable in Germany"

The gate

Put the middleware on your own checkout route. It refuses a buyer with no country, no company name, an EU buyer without a confirmed VAT ID, and a third-country buyer who has not said they are buying as a business:

On admission it merges the frozen check into the request as vat_id_check. Put that into the payment's meta and the invoice reads it from there — nothing asks the confirmation service twice, and nothing looks it up again at render time.

POST /!/invoices/buyer-check answers the same question for a form while the buyer is typing. It is a convenience, not the gate: a client can ignore it, and the middleware asks again on the server.

When the confirmation service is down

The purchase goes through. The check comes back pending, the invoice says "VAT ID provided, verification pending", and the case appears under Utilities → USt-IdNr.-Prüfungen in the Control Panel. php artisan invoices:recheck-vat-ids asks again later and writes what it found into invoice_vat_id_checks — never into the invoice, which does not change. The command exits non-zero only when a number that was pending now comes back invalid, so it can sit in a schedule without teaching anybody to ignore it.

The distinction it is built around: valid: false is an answer and means invalid; a timeout, a 500, an unreadable body or VIES' own MS_UNAVAILABLE inside an HTTP 200 are non-answers and mean pending. Collapsing the two would tell a business with a correct number that it is wrong.

Configuration

Bind Contracts\VatIdVerifier to your own implementation to use the German BZSt enquiry (§ 18e UStG) instead of VIES.

Selling to consumers in other EU countries (OSS)

Once your B2C turnover into other member states passes €10,000 a year, a consumer in Austria pays Austrian VAT. Switch tax.oss.destination_taxation on for that, and either write a zone for every country you sell into, or let the addon supply the standard rates:

Support\EuStandardRates holds the standard rate of all 27 member states with the date they were read (AS_OF) and their sources. Every line taxed from that table says so in its notes. A zone you write for a country always beats the table, which is how you correct a rate before the addon catches up. Standard rates only: a product in a reduced class still needs a zone of its own, because reduced rates differ by country and by kind of supply. A business with a confirmed VAT ID still gets reverse charge, and tax.small_business still switches everything off. Check the rates of the countries you actually sell into; this is the addon's reading of published tables, not tax advice.

Every invoice line keeps the mechanism and the place of supply the rules decided (tax_mechanism, place_of_supply), so the tax report can say where the tax is owed a year later.

Exports for tax and bookkeeping

Control Panel → Utilities → Invoice export (permission access invoice-exports utility), for the brand you are looking at:

Text columns that start with =, +, -, @, a tab or a carriage return get a leading apostrophe, so a spreadsheet opens them as text rather than running them as a formula.

Upgrading to 2.2: run php artisan migrate before the next sale. The writer fills two new columns on invoice_items, and an invoice written before the migration has run fails.

The same from the command line, with any combination of delimiter, encoding and decimal mark:

Periods: --from/--to, --month, --quarter, --year, in the application's time zone. Lines written before 2.2 carry no mechanism and place of supply; the export derives both from the document and says how many it derived.

What it deliberately does not do


All versions of statamic-invoices with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
dompdf/dompdf Version ^3.1
goldnead/statamic-brand-context Version ^1.13
goldnead/statamic-payments Version ^1.14
laravel/framework Version ^12.40|^13.0
statamic/cms Version ^6.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package goldnead/statamic-invoices contains the following files

Loading the files please wait ...