Laravel SISP 2.1 separates the buyer from the customer
A checkout has two identities: the person who pays and the entity the invoice belongs to. Version 2.1 stops collapsing them into one.
A manager pays for a company subscription with a personal card. The contact details belong to the manager. The invoice has to carry the company name, the company NIF and the company fiscal address, because that document exists for the tax authority, not for the inbox.
That is two identities moving through one checkout. Until Laravel SISP 2.1, the package only knew one of them.
The buyer on an invoice is a fiscal identity. The customer is a contact. They overlap often enough that a payment package can pretend they are the same field, and the pretending holds until the first accountant asks why the invoice says a person’s name.
Four fields, and none of them replace anything
Version 2.1 adds the fiscal identity next to the contact identity instead of on top of it.
src/ValueObjects/CustomerData.php
final readonly class CustomerData
{
public function __construct(
public string $name,
public ?string $email = null,
// ...
public ?string $vat = null,
public ?string $taxName = null,
public ?string $taxEntityType = null,
public ?string $taxAddress = null,
) {}
}
name, email, phone and address keep meaning what they meant. taxName, vat, taxEntityType and taxAddress describe the entity that owes the money. The value object carries both, the request validates both as sometimes|nullable, and the transaction and the invoice both store both.
Storing the fiscal identity twice is deliberate. An invoice is a document, and a document has to stay true after the transaction it came from is updated. Copying the fields onto the invoice at generation time is the cost of that guarantee.
The PDF decides late
The interesting code is not the schema. It is the moment the document has to choose which name to print.
src/Actions/GenerateInvoicePdfAction.php
$taxName = $invoice->customer_tax_name ?? $transaction->customer_tax_name;
$taxAddress = $invoice->customer_tax_address ?? $transaction->customer_tax_address;
$buyer = EntityBuilder::make()
->name($taxName ?? $invoice->customer_name ?? $transaction->customer_name ?? '')
->address($taxAddress ?? $invoice->customer_address ?? $transaction->customer_address ?? '')
->vat($invoice->customer_vat ?? $transaction->customer_vat ?? '')
The buyer block prefers the fiscal identity and falls back to the contact. Two chains, both resolved at render time, neither of them requiring the merchant to know in advance which kind of buyer is paying.
That fallback is also the upgrade path. A merchant who sends nothing new gets the same PDF as before, byte for byte. A merchant who sends customer_tax_name and customer_vat gets a document an accountant can file. No migration of intent, no flag to flip.
The cheaper option that does not work
The obvious counter is to rename the existing columns. Treat customer_name as the fiscal name, tell merchants to send the company, and skip four columns.
It fails on the first support ticket. Support needs to reach the manager. Finance needs the company. A single column answers one of those questions and lies about the other, and the lie surfaces at the worst moment, which is the moment someone is reconciling a payment they cannot find.
The four columns are not schema growth for its own sake. They are the package admitting that a payment has a payer and an invoice has an owner, and that those are separate facts about the same transaction.
Model the entity that owes the money, not the one that clicked pay.