KSeF Pro M2DocumentationSending Invoices to KSeF

Sending Invoices to KSeF

KSeF Pro for Magento 2 generates FA(2) XML from Magento invoice objects, signs them with XAdES-BES digital signatures, and manages the full KSeF session lifecycle using Magento's native cron infrastructure and observer pattern.

Automatic Submission Flow

1. Magento invoice created (Admin UI, REST API, or GraphQL)
2. sales_order_invoice_save_after observer fires
3. Rule engine evaluates invoice (store scope, NIP, currency, customer group)
4. FA(2) XML generated from Magento invoice data
5. XML signed with XAdES-BES (PHP OpenSSL)
6. Job inserted into ksefpl_queue (status: PENDING)
7. Observer returns — invoice save completes instantly

--- Magento Cron (background) ---
8. ksefpl_process_queue (every 2 min): opens KSeF session, submits batch
9. ksefpl_poll_status (every 1 min): polls MF for session processing status
10. ksefpl_download_upo (every 3 min): downloads UPO when processingCode=200
11. KSeF-ID written to ksefpl_invoice_registry and Magento invoice extension attribute

Steps 1–6 complete in milliseconds during the invoice save. Steps 8–11 run asynchronously in the background. The Admin user and REST API caller never wait for KSeF API latency.

FA(2) XML Generation

KSeF Pro maps Magento invoice data to the FA(2) logical structure:

| Magento Source | FA(2) Element | Notes | |---|---|---| | Invoice::getCreatedAt() | <DataWystawienia> | Converted to Europe/Warsaw timezone | | KSeF Pro seller config | <Podmiot1> | NIP, company name, address | | Order::getCustomTaxvat() + billing address | <Podmiot2> | B2B: NIP; B2C: BRAK identifier | | InvoiceItem collection | <FaWiersz> rows | One row per SKU | | Tax amounts by rate | <StawkaPodatku> groups | Aggregated by mapped Polish VAT code | | Invoice::getGrandTotal() | <P_15> | Total gross in PLN | | Order::getOrderCurrencyCode() | <KodWaluty> + <KursWaluty> | Non-PLN orders only | | Invoice::getIncrementId() | <NrFaKorygowanej> | Correction invoices (KOR) only |

Tax Amount Aggregation

Magento stores tax amounts per tax rule — each rule can apply a different rate to different product categories. KSeF Pro aggregates these by the Polish VAT code mapped to each rule:

Magento tax rule "Standard 23%" → mapped to code "23"
Magento tax rule "Software Export 0%" → mapped to code "0"

FA(2) output:
<StawkaPodatku>
  <Stawka>23</Stawka>
  <Podstawa>1000.00</Podstawa>
  <Kwota>230.00</Kwota>
</StawkaPodatku>
<StawkaPodatku>
  <Stawka>0</Stawka>
  <Podstawa>500.00</Podstawa>
  <Kwota>0.00</Kwota>
</StawkaPodatku>

If two Magento tax rules are mapped to the same Polish code, their amounts are summed into a single <StawkaPodatku> group. This is correct — FA(2) requires one group per rate, not per Magento rule.

XML Preview in Admin

Before enabling production, review the generated FA(2) for an existing invoice:

  1. Go to Sales → Invoices and open any invoice
  2. Click "Preview FA(2) XML" in the KSeF Pro panel (right column)
  3. Verify: seller NIP, buyer NIP or BRAK, line items, <StawkaPodatku> groups, and <P_15> total

XML signing and preview in Magento KSeF Pro

Pay particular attention to:

  • <P_15> matching the Magento invoice grand total (in PLN)
  • Each <FaWiersz> row using the correct <Stawka> code
  • <Podmiot1> NIP matching your seller configuration
  • <Podmiot2> containing the buyer NIP for B2B orders

XAdES-BES Signing

Before submission, KSeF Pro signs each FA(2) document with an XAdES-BES enveloped signature:

  1. Canonicalize the FA(2) XML using the C14N algorithm
  2. Compute SHA-256 digest of the canonical form
  3. Derive signing key material from the KSeF token (decrypted via EncryptorInterface)
  4. Sign with RSA-SHA256 using PHP openssl_sign()
  5. Embed the <ds:Signature> block in the FA(2) XML before the closing root tag
  6. Base64-encode the entire signed document for API transport

No qualified certificate (kwalifikowany podpis elektroniczny) is required for invoice signing. The KSeF portal token provides sufficient key material for XAdES-BES. Qualified certificates are only required for token issuance.

The signing service is PlugKit\KsefPl\Service\XmlSigner. If signing fails (error 10100), check PHP OpenSSL:

php -r "echo openssl_get_curve_names() ? 'OpenSSL OK' : openssl_error_string();"
php -m | grep openssl

Cron-Based Session Lifecycle

ksefpl_process_queue (every 2 minutes)

  1. Queries ksefpl_queue for up to 50 PENDING jobs
  2. Opens a KSeF session:
    POST /api/online/Session/InitSigned
    ← { sessionToken, referenceNumber }
    
  3. Submits each invoice:
    POST /api/online/Session/Invoice/Send
    → { invoiceHash: {...}, invoicePayload: { type: "plain", invoiceBody: "BASE64_XML" } }
    ← { elementReferenceNumber }
    
    Each elementReferenceNumber is stored in ksefpl_queue alongside the invoice ID.
  4. Closes the session:
    POST /api/online/Session/CloseSigned
    ← { processingCode: 200 }
    
  5. Updates jobs from PENDING to SUBMITTED
  6. Stores the session referenceNumber in ksefpl_sessions

ksefpl_poll_status (every 1 minute)

For each SUBMITTED session:

GET /api/common/Status/{referenceNumber}
← { processingCode: 200, upoReferenceNumber: "..." } when ready

When processingCode=200, marks the session as ready for UPO download.

ksefpl_download_upo (every 3 minutes)

For each session with UPO available:

GET /api/common/Upo/{upoReferenceNumber}
← UPO XML
  1. Saves UPO to var/ksefpl/upo/{year}/{month}/{invoice-id}.xml
  2. Extracts KSeF-IDs for each invoice in the session
  3. Writes KSeF-IDs to ksefpl_invoice_registry and to the Magento invoice's ksefpl_ksef_id extension attribute
  4. Updates audit log entries to ACCEPTED
  5. Dispatches ksefpl_ksef_id_received Magento event (consumed by Fakturownia Pro in Paired Mode)

Batch session panel in Magento KSeF Pro

Session Timeout Risk

KSeF sessions expire after 30 minutes of inactivity. Risk factors:

  • Large batches (50 invoices) with slow Magento processing take longer than expected
  • MF server 503 responses during maintenance delay session close

Mitigation: enable "One invoice per session" in Advanced settings. Each invoice gets its own session — close happens within seconds of submit, making timeout impossible.

Manual Submission

Submit individual invoices immediately from the Admin:

  1. Go to Sales → Invoices and open an invoice
  2. In the KSeF Pro panel, click "Submit to KSeF now"
  3. KSeF Pro opens a single-invoice session synchronously — the Admin page waits for completion
  4. Result (KSeF-ID or error code) appears in the panel immediately

Or via CLI:

php bin/magento ksefpl:submit --invoice-id=INV-00001234

Credit Memos (Correction Invoices)

When a Magento credit memo is created for an order with a KSeF-ID, KSeF Pro generates a correction invoice (KOR):

<RodzajFaktury>KOR</RodzajFaktury>
<DaneFaKorygowanej>
  <DataFaKorygowanej>2025-03-01</DataFaKorygowanej>
  <NrFaKorygowanej>ORIGINAL_KSEF_ID</NrFaKorygowanej>
</DaneFaKorygowanej>

The correction is submitted to KSeF via the same cron session lifecycle and receives its own KSeF-ID. Both the original and correction appear in the audit log as linked records.

If no KSeF-ID exists for the order (invoice was never submitted or submission failed), the credit memo is queued but cannot be submitted until the original invoice has a KSeF-ID. The queue entry stays in BLOCKED_NO_KSEF_ID status until the parent submission completes.

Magento Cron Configuration

KSeF Pro cron jobs run under Magento's default cron group. Verify cron is active:

# Check job registration
php bin/magento cron:run --group=default --dry-run 2>&1 | grep ksefpl
 
# View execution history
# Admin: System → Tools → Cron Schedule → filter by job_code = ksefpl_*

For production environments, the server crontab must include:

# /etc/cron.d/magento
* * * * * www-data php /var/www/magento/bin/magento cron:run --group=default 2>&1

On Adobe Commerce Cloud, Magento cron is configured automatically — no server crontab changes needed.

Cron Queue Diagnostic Commands

# Show queue statistics
php bin/magento ksefpl:queue-status
 
# Process queue immediately (bypasses cron schedule)
php bin/magento ksefpl:process-queue
 
# Retry all failed submissions
php bin/magento ksefpl:retry-failed
 
# Test KSeF connection
php bin/magento ksefpl:test-connection

Currency Conversion

For non-PLN Magento stores:

| Scenario | KSeF Pro Behaviour | |---|---| | PLN order | Amounts used directly; no <KursWaluty> | | EUR order, rate stored on order | Stored rate used; <KursWaluty> populated | | EUR order, no stored rate | NBP API fetched at submission time | | Unsupported currency | Manual fallback rate from Advanced settings; warning logged |

The NBP rate is fetched from https://api.nbp.pl/api/exchangerates/rates/A/{currency}?format=json. Exchange rates are cached in Magento's cache backend for 24 hours. Keep Magento's built-in currency import running (Stores → Currency → Import) to maintain accurate rates in directory_currency_rate.

Next Steps

Edit this page on GitHub
Was this page helpful?

Powiązana dokumentacja

Ten temat jest dostępny również dla innych platform: