API reference

All classes live in the namespace Darvis\UblPeppol. Every add...() method returns the builder, so calls can be chained. Arguments are positional; PHP named arguments work as well.

The two builders side by side

UblNlBis3Service builds Dutch invoices and credit notes, UblBeBis3Service builds Belgian ones. The method names are the same; the arguments are not always.

Document

Method UblNlBis3Service UblBeBis3Service
createDocument(): self Starts an <Invoice>. Throws a RuntimeException on a second call The same
generateXml() without createDocument() Throws a RuntimeException Returns an empty XML declaration
generateXml(bool $validateFirst = false): string Puts the elements in schema order and returns the XML. With true it calls validate() first and throws an InvalidArgumentException on errors Returns the XML. With true the same, plus suggested corrections in the message. On a credit note it always checks the credit note rules
validate(): InvoiceValidationResult Code formats and five Dutch rules Totals and code formats. See Validation
enableStrictCodelistValidation(?string $jsonPath = null, ?CodelistRegistry $registry = null): self See Strict code lists The same

Header and references

addInvoiceHeader(string $invoiceNumber, $issueDate, $dueDate): self
addAccountingCost(string $value): self                 // BT-19, optional
addBuyerReference(?string $buyerRef = 'BUYER_REF'): self
addOrderReference(string $orderNumber = 'PO-001'): self
addAdditionalDocumentReference(string $id, ?string $documentType = null): self

The same in both builders, with two differences: the Dutch builder ignores $documentType, and the Belgian addBuyerReference() throws on a credit note. The date arguments have no type declaration in the code; a YYYY-MM-DD string or a \DateTime is accepted.

Parties

UblNlBis3Service:

addAccountingSupplierParty(
    string $endpointId,
    string $endpointSchemeID,
    string $partyId,
    string $partyName,
    string $street,
    string $postalCode,
    string $city,
    string $countryCode,
    string $companyId,                  // the supplier's VAT number
    ?string $additionalStreet = null
): self

addAccountingCustomerParty(
    string $endpointId,
    string $endpointSchemeID,
    string $partyId,
    string $partyName,
    string $street,
    string $postalCode,
    string $city,
    string $countryCode,
    ?string $additionalStreet = null,
    ?string $companyId = null,          // the customer's registration number (KvK)
    ?string $contactName = null,
    ?string $contactPhone = null,
    ?string $contactEmail = null,
    ?string $vatNumber = null,          // with the country prefix
    string $taxSchemeId = 'VAT'
): self

Only in UblNlBis3Service, for the legal registration identifier (BT-30, BT-47):

addSupplierLegalRegistration(string $identifier, string $schemeId = '0106'): self
addCustomerLegalRegistration(string $identifier, string $schemeId = '0106'): self

See Dutch invoices.

The customer’s $partyId (BT-46) is whatever you call this customer, for example your own customer number. The Dutch builder writes the endpoint’s scheme on it only when it is the endpoint identifier itself, 8 digits under scheme 0106 (a KvK number) or 20 digits under 0190 (an OIN); otherwise the optional schemeID is left out. Until 1.9 every value got the scheme, so CUST-710 went out as a KvK number and the receiver’s validation answered with PEPPOL-COMMON-R054.

UblBeBis3Service has the same positions with other names: $name for $partyName, $country for $countryCode, $vatNumber for the supplier’s $companyId, and $registrationNumber for the customer’s $companyId. It has no $taxSchemeId argument.

Delivery, payment, allowances and charges

UblNlBis3Service:

addDelivery(
    string $deliveryDate,
    ?string $locationId = null,
    string $locationSchemeId = '0088',
    ?string $street = null,
    ?string $additionalStreet = null,
    ?string $city = null,
    ?string $postalCode = null,
    ?string $countryCode = null,
    ?string $partyName = null
): self

addPaymentMeans(
    string $paymentMeansCode = '30',
    string $paymentMeansName = 'Credit transfer',
    ?string $paymentId = null,
    ?string $accountId = null,              // IBAN
    ?string $accountName = null,
    ?string $financialInstitutionId = null, // BIC
    ?string $paymentChannelCode = null,
    ?string $paymentDueDate = null
): self

addPaymentTerms(?string $note = null): self

addAllowanceCharge(
    bool $isCharge = true,
    float $amount = 0.0,
    string $reason = '',
    string $taxCategoryId = 'S',
    float $taxPercent = 0.0,
    string $currency = 'EUR'
): self

UblBeBis3Service:

addDelivery(
    string $deliveryDate,
    ?string $locationId = null,
    string $locationSchemeId = '0088',
    ?string $street = null,
    ?string $additional_street = null,
    ?string $city = null,
    ?string $postal_code = null,
    ?string $country = null,
    ?string $party_name = null
): self

addPaymentMeans(
    string $paymentMeansCode,
    ?string $paymentMeansName,
    string $paymentId,
    string $account_iban,
    ?string $account_name,
    ?string $bic,
    ?string $channel_code = null,           // not written
    ?string $due_date = null                // not written
): self

addPaymentTerms(
    ?string $note = null,
    ?float $discount_percent = null,        // not written
    ?float $discount_amount = null,         // not written
    ?string $discount_date = null           // not written
): self

addAllowanceCharge(
    bool $isCharge,
    float $amount,
    string $reason,
    string $taxCategoryId,
    float $taxPercent,
    string $currency
): self

Totals and lines

addTaxTotal(array $taxes): self
addLegalMonetaryTotal(array $amounts, string $currency = 'EUR'): self   // Dutch builder
addLegalMonetaryTotal(array $totals, string $currency): self            // Belgian builder
addInvoiceLine(array $lineData): self

The array keys are listed under Dutch invoices and Belgian invoices. An entry of addTaxTotal() also takes tax_exemption_reason_code (BT-121) and tax_exemption_reason (BT-120); see VAT categories.

Credit notes, in both builders

Method What it does
createCreditNoteDocument(): self Starts a <CreditNote> instead of an <Invoice>
addCreditNoteHeader(string $creditNoteNumber, $issueDate): self The header with type code 381. No due date. $issueDate is a YYYY-MM-DD string or a \DateTime; the Dutch builder refuses a date in the future
addBillingReference(string $originalInvoiceNumber, ?string $originalIssueDate = null): self The invoice the credit note corrects. Required on a credit note. The Dutch builder throws on an empty number or a date that is not YYYY-MM-DD
addCreditNoteLine(array $lineData): self A line with <cbc:CreditedQuantity>. Makes quantity, price and line amount positive
isCreditNote(): bool true after createCreditNoteDocument()

In the Dutch builder since 1.10.0. There, the invoice methods throw a RuntimeException on a credit note and the credit note methods throw one on an invoice. See Credit notes.

Only in UblBeBis3Service

Method What it does
calculateTotals(): array Adds up the lines added so far. Returns totals, tax_totals and total_tax_amount
getInvoiceLines(): array, getTotals(): array, getTaxTotals(): array What you passed in so far

See Let the builder add up the lines.

Vat\VatCategory

A string backed enum with the nine VAT categories PEPPOL allows outside Italy: StandardRate (S), ZeroRated (Z), Exempt (E), ReverseCharge (AE), IntraCommunitySupply (K), ExportOutsideEu (G), NotSubjectToVat (O), CanaryIslands (L), CeutaMelilla (M). See VAT categories.

Method Returns
VatCategory::fromCode(string $code): ?self The category, in any case; null for an unknown code
label(): string The name in the UNCL5305 code list
description(): string When to use it
requiresExemptionReason(): bool, forbidsExemptionReason(): bool Whether the VAT breakdown needs or refuses a reason
defaultExemptionReasonCode(): ?string VATEX-EU-AE, VATEX-EU-IC, VATEX-EU-G or VATEX-EU-O; null for the others
exemptionReasonText(string $language = 'en'): ?string The standard text in en, nl or fr
requiresZeroRate(): bool Whether the rate must be 0
rules(): array The EN 16931 rules of the category, rule code => text
ruleId(int $number): string The code of a numbered rule: ruleId(10) is BR-IC-10 for K
VatCategory::guide(string $language = 'en'): array All of the above for every category

Vat\VatExemptionReason

The VATEX code list (BT-121). Constants REVERSE_CHARGE, INTRA_COMMUNITY_SUPPLY, EXPORT_OUTSIDE_EU, NOT_SUBJECT_TO_VAT.

VatExemptionReason::isKnown(string $code): bool
VatExemptionReason::name(string $code): ?string
VatExemptionReason::categoryOf(string $code): ?VatCategory
VatExemptionReason::codes(): array

Validation\InvoiceValidationResult

Returned by validate(). Public read-only properties: bool $isValid, array $errors, array $warnings, array $corrections.

Method Returns
isValid(): bool true without errors
hasErrors(): bool, hasWarnings(): bool  
getErrorsAsString(string $separator = "\n"): string  
getWarningsAsString(string $separator = "\n"): string  
getCorrections(): array Suggested totals. Nothing is applied to the document
getCorrection(string $key): mixed One suggested value, or null
toArray(): array is_valid, errors, warnings, corrections
InvoiceValidationResult::success(): self A valid result
InvoiceValidationResult::fromException(\Throwable $e): self An invalid result with the exception message as its only error

Validation\CreditNoteValidationException

Thrown by generateXml() when a credit note breaks a rule; extends InvalidArgumentException. The first line of the message names every failing rule.

Method Returns
getErrors(): array A list of ['rule' => string, 'message' => string], in the order they were checked
getRuleIds(): array The failing rule ids, each once

Rules\RuleCatalog and Rules\Rule

The official rules of the OpenPEPPOL release the validator runs and the UBL-PEPPOL- rules of this package. See MCP server.

Method Returns
RuleCatalog::find(string $id): ?Rule The rule; brackets and case do not matter
RuleCatalog::search(string $query, int $limit = 10): array Rules whose id starts with the query, then rules holding every word of it
RuleCatalog::release(): string The OpenPEPPOL release, such as 2026.5

Rule has the read-only properties id, flag (fatal or warning), text, test and context (both null when unknown), source (CEN, PEPPOL or package) and advice, and the methods isFatal(), isOfficial() and url().

Validation\UblValidator

Static helpers for single values. The full table is under Validation.

UblValidator::validateVatNumber(?string $vatNumber): ?string
UblValidator::isValidVatNumber(string $vatNumber): bool
UblValidator::validateIban(?string $iban): ?string
UblValidator::isValidUnitCode(string $unitCode): bool
UblValidator::isValidTaxCategory(string $categoryId): bool
UblValidator::isValidCurrencyCodeFormat(string $currencyCode): bool
UblValidator::isValidSchemeIdFormat(string $schemeId): bool
UblValidator::isValidPaymentMeansCodeFormat(string $paymentMeansCode): bool
UblValidator::isValidClassificationScheme(string $schemeId): bool
UblValidator::getClassificationSchemeDescription(string $schemeId): string
UblValidator::validateInvoiceData(array $data): array
UblValidator::validateBasicCodes(array $codes): InvoiceValidationResult
UblValidator::validateStrictCodelists(array $codes, CodelistRegistry $registry): InvoiceValidationResult
UblValidator::resolveTaxExemption(string $categoryId, ?string $code = null, ?string $text = null): array
UblValidator::validateVatCategories(DOMDocument $document): InvoiceValidationResult
UblValidator::validateInvoiceTotals(array $invoiceLines, array $totals, array $taxTotals, float $allowanceTotalAmount = 0.0, float $chargeTotalAmount = 0.0, float $prepaidAmount = 0.0, array $documentAllowances = [], array $documentCharges = []): InvoiceValidationResult

The last five are what the builders call: addTaxTotal() settles the exemption reason with resolveTaxExemption(), validate() calls the others.

Validation\CodelistRegistry

new CodelistRegistry(array $lists)                      // ['iso4217' => ['EUR'], ...]
CodelistRegistry::fromJsonFile(string $path): self      // throws when the file is missing or not JSON
$registry->isLoaded(string $listName): bool             // false for a missing or empty list
$registry->has(string $listName, string $code): bool    // codes are compared in upper case

Constants\UnitCodes

UnitCodes::isValid(string $code): bool
UnitCodes::getDescription(string $code): ?string        // 'hour' for 'HUR'
UnitCodes::getAll(): array                              // every code the package knows

ViesService

checkVat(string $countryCode, string $vatNumber): array
checkFullVatNumber(string $fullVatNumber): array

Needs the soap extension. See VAT numbers.

CompanyRegistrationService

validate(string $number, string $countryCode): array
getSupportedCountries(): array

See Company numbers.

PeppolService (Laravel)

sendUblXml(string $ublXml, ?string $invoiceNumber = null): array
sendInvoice(object $invoice, string $ublXml): array
testConnection(): array
getConfig(): array          // url, username, password_configured

The constructor takes no arguments; it reads the ubl-peppol config. See Sending invoices.

Models\PeppolLog (Laravel)

PeppolLog::tableExists(): bool
PeppolLog::cleanupOldLogs(int $days = 60): int

// query scopes
PeppolLog::success();
PeppolLog::error();
PeppolLog::pending();
PeppolLog::recent(int $days = 60);
PeppolLog::olderThan(int $days);

See The log table is optional.

UblPeppolConfig (Laravel)

The one class that reads the config: UblPeppolConfig::url(), username(), password() (each a string, empty when not set), logRetentionDays() (an integer, default 60), mcpEnabled() (default true) and mcpHandle() (default ubl-peppol).

Artisan

php artisan peppol:cleanup [--days=]

Publish tags

Tag Publishes
ubl-peppol-config config/ubl-peppol.php
ubl-peppol-migrations The migration for the peppol_logs table