feat(peppol): Qvalia access-point adapter, send flow and delivery webhook (#1780)
* feat(peppol): Qvalia access-point adapter, send flow and delivery webhook Qvalia is the contracted Peppol Access Point (signed 2026-08-21). This fills the provider-neutral PeppolTransport seam from #1595 with a real adapter and turns the disabled "Skicka via Peppol" menu item into a working send flow. Adapter (lib/invoices/transports/qvalia.ts): partner-scoped recipient lookup, XML submission to /invoices/outgoing with integrationId correlation, 409 recovery only when the stored copy carries the same seller endpoint, tolerant mapping of Qvalia's free-text webhook statuses onto the 11-state lifecycle, constant-time shared-secret webhook verification (Qvalia does not sign webhooks), and evidence retrieval of the message-log status plus Qvalia's stored XML copy. Registered from the environment in lib/init.ts; switched on per deployment with PEPPOL_TRANSPORT_PROVIDER=qvalia. POST /api/invoices/[id]/peppol/send: stage the exact XML, look up the recipient, record recipient_verified and submitting, submit, record submission_accepted, then issue a draft with the mark-sent semantics (issueAndBookInvoice) only after the network accepted it. A sync rejection is a terminal failed event so the identical document is never re-sent; an operational failure is retryable; an already-submitted XML replays idempotently. POST /api/webhooks/peppol/qvalia resolves the delivery by integrationId, persists the verified event via the service-role RPC and stores evidence best-effort; unknown submissions answer 200, our own persistence failures 500. UI: the send item is availability-driven with a confirm dialog, the invoice page shows the latest Peppol status, and drafts can be sent (the number is assigned server-side). Probe script for the first sandbox contact under scripts/peppol/qvalia-probe.ts. Refs #546 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqFpxeWqbpR7bcwUJLRERQ * fix(peppol): Qvalia sandbox facts from first live contact: bare-key auth, api-test host, SMP-URL document types The onboarding mail and a live probe against the sandbox (partner SE5595386219) corrected three assumptions from the public docs: the key is accepted bare in the Authorization header (the ApiKey prefix answers 401), the sandbox host is api-test.qvalia.com, and the recipient lookup returns document types as SMP service URLs, so capabilities are now normalized to bare Peppol document type ids before comparison. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqFpxeWqbpR7bcwUJLRERQ * feat(peppol): probe commands to inspect and configure the Qvalia webhook subscription Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqFpxeWqbpR7bcwUJLRERQ * fix(peppol): decode UBL entities in one pass (CodeQL js/double-escaping) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TqFpxeWqbpR7bcwUJLRERQ --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
8249fcab5e
commit
05c3c6ebd9
@@ -4028,6 +4028,31 @@
|
||||
"peppol_prepared_description": "An immutable XML copy was saved for traceability. The invoice has not been sent to Peppol.",
|
||||
"peppol_prepare_failed_title": "Could not prepare Peppol delivery",
|
||||
"peppol_prepare_failed_description": "The invoice could not be saved as a prepared Peppol delivery.",
|
||||
"peppol_send_confirm_title": "Send via Peppol?",
|
||||
"peppol_send_confirm_description": "The invoice is sent as an e-invoice to Peppol id {recipient}. A draft gets its invoice number, is marked as sent and is booked once the Peppol access point has accepted it.",
|
||||
"peppol_send_confirm_action": "Send",
|
||||
"peppol_sending": "Sending via Peppol…",
|
||||
"peppol_sent_title": "Sent via Peppol",
|
||||
"peppol_sent_description": "The invoice has been handed to the Peppol network. Delivery status updates here when the recipient has received it.",
|
||||
"peppol_already_sent_description": "This invoice has already been sent via Peppol. It is not sent again.",
|
||||
"peppol_issue_failed_description": "The invoice was sent via Peppol but could not be marked as sent. Use Mark as sent to complete the bookkeeping.",
|
||||
"peppol_send_failed_title": "Could not send via Peppol",
|
||||
"peppol_send_failed_description": "The invoice has not been sent via Peppol.",
|
||||
"peppol_status_title": "Peppol",
|
||||
"peppol_status_recipient": "Recipient",
|
||||
"peppol_status_label": "Status",
|
||||
"peppol_status_updated": "Updated",
|
||||
"peppol_status_staged": "Prepared, not sent",
|
||||
"peppol_status_recipient_verified": "Recipient verified",
|
||||
"peppol_status_submitting": "Sending",
|
||||
"peppol_status_retryable_failure": "Temporary failure, try again",
|
||||
"peppol_status_submission_accepted": "Accepted by the Peppol access point",
|
||||
"peppol_status_transport_succeeded": "Delivered to the recipient's access point",
|
||||
"peppol_status_recipient_acknowledged": "Acknowledged by the recipient",
|
||||
"peppol_status_business_accepted": "Approved by the recipient",
|
||||
"peppol_status_business_rejected": "Rejected by the recipient",
|
||||
"peppol_status_no_route": "Recipient has no Peppol registration",
|
||||
"peppol_status_failed": "Failed",
|
||||
"pdf_rerender_downloaded_title": "Freshly generated PDF downloaded",
|
||||
"pdf_rerender_preview_title": "Showing a freshly generated PDF",
|
||||
"pdf_preview_blocked_title": "Could not open the preview",
|
||||
|
||||
@@ -4028,6 +4028,31 @@
|
||||
"peppol_prepared_description": "En oföränderlig XML-kopia har sparats för spårbarhet. Fakturan har inte skickats till Peppol.",
|
||||
"peppol_prepare_failed_title": "Kunde inte förbereda Peppol-leveransen",
|
||||
"peppol_prepare_failed_description": "Fakturan kunde inte sparas som en förberedd Peppol-leverans.",
|
||||
"peppol_send_confirm_title": "Skicka via Peppol?",
|
||||
"peppol_send_confirm_description": "Fakturan skickas som e-faktura till Peppol-id {recipient}. Ett utkast får fakturanummer, markeras som skickat och bokförs när Peppol-operatören tagit emot den.",
|
||||
"peppol_send_confirm_action": "Skicka",
|
||||
"peppol_sending": "Skickar via Peppol…",
|
||||
"peppol_sent_title": "Skickad via Peppol",
|
||||
"peppol_sent_description": "Fakturan har lämnats till Peppol-nätverket. Leveransstatus uppdateras här när mottagaren tagit emot den.",
|
||||
"peppol_already_sent_description": "Den här fakturan är redan skickad via Peppol. Den skickas inte igen.",
|
||||
"peppol_issue_failed_description": "Fakturan skickades via Peppol men kunde inte markeras som skickad. Använd Markera som skickad för att slutföra bokföringen.",
|
||||
"peppol_send_failed_title": "Kunde inte skicka via Peppol",
|
||||
"peppol_send_failed_description": "Fakturan har inte skickats via Peppol.",
|
||||
"peppol_status_title": "Peppol",
|
||||
"peppol_status_recipient": "Mottagare",
|
||||
"peppol_status_label": "Status",
|
||||
"peppol_status_updated": "Uppdaterad",
|
||||
"peppol_status_staged": "Förberedd, inte skickad",
|
||||
"peppol_status_recipient_verified": "Mottagare verifierad",
|
||||
"peppol_status_submitting": "Skickas",
|
||||
"peppol_status_retryable_failure": "Tillfälligt fel, försök igen",
|
||||
"peppol_status_submission_accepted": "Mottagen av Peppol-operatören",
|
||||
"peppol_status_transport_succeeded": "Levererad till mottagarens operatör",
|
||||
"peppol_status_recipient_acknowledged": "Bekräftad av mottagaren",
|
||||
"peppol_status_business_accepted": "Godkänd av mottagaren",
|
||||
"peppol_status_business_rejected": "Avvisad av mottagaren",
|
||||
"peppol_status_no_route": "Mottagaren saknar Peppol-registrering",
|
||||
"peppol_status_failed": "Misslyckades",
|
||||
"pdf_rerender_downloaded_title": "Nyskapad PDF nedladdad",
|
||||
"pdf_rerender_preview_title": "Nyskapad PDF visas",
|
||||
"pdf_preview_blocked_title": "Kunde inte öppna förhandsgranskningen",
|
||||
|
||||
Reference in New Issue
Block a user