PRD: E-invoice provider integration (iiapi / T-VAN)
| Module | Tax & Invoice (CORE-10) | PRD ID | PRD-INV-002 |
| Status | Shipped | Owner | Tax & Invoice squad |
| Date | 2026-04-13 | Version | v1.0 |
| Packages | @nx/invoice · @nx/iiapi · @nx/t-van · @nx/core | URD | CFG · INV |
TL;DR
Lets a merchant connect a legal Vietnamese e-invoice provider (VNPAY / VNIS via iiapi) once, with credentials stored encrypted, then issue numbered e-invoices automatically the moment a payment succeeds. A single provider seam handles both seller tax-info lookup and invoice issuance, so every completed sale can become a compliant invoice - captured with its tax-authority code and tracked end-to-end - without manual paperwork.
1. Context & Problem
Merchants selling in Vietnam must issue legal electronic invoices and submit them to the tax authority. Before this increment KICKO resolved seller tax info through a direct T-VAN integration and had no first-class invoice package, so there was no way to connect an e-invoice provider per merchant, store its credentials safely, or turn a completed payment into an issued invoice with an auditable status.
This increment stands up a dedicated invoice package and the iiapi provider client, connecting a provider per merchant, and migrates tax-info resolution from T-VAN onto iiapi so a single provider seam handles both tax-info lookup and invoice issuance. This is the foundation for legal e-invoicing - a hard regulatory requirement for the HKD/SME segment KICKO targets.
2. Goals & Non-Goals
Goals
- Stand up the invoice package and the iiapi provider client; connect a provider per merchant via
merchantId/ connection link. - Store provider credentials encrypted and require an explicit
clientName(no implicit default client). - Configure issuance per invoice type (serial / category / policy) and route sale channels to a provider config, with a guided onboarding flow.
- Drive the issuance lifecycle from a successful payment through Kafka + BullMQ queues, capturing the invoice number and tax-authority (CQT) code.
- Process inbound provider webhooks (with a per-config webhook toggle) to update invoice status.
- Migrate tax-info resolution from T-VAN to iiapi as the single provider seam.
Non-Goals
- Multiple invoice providers beyond the current iiapi / T-VAN set.
- PDF rendering of the invoice (produced provider-side).
- Tax-rate calculation at sale time (owned by the pricing engine).
- Tax declaration / filing automation.
3. Success Metrics
| Metric | Target / signal |
|---|---|
| Issuance coverage | Successful payments that produce an issued invoice in the configured mode |
| Issuance reliability | Issuance success rate after the retry policy; failures land in the audit trail, never silently dropped |
| Credential safety | 100% of provider credentials stored encrypted; zero plaintext at rest |
| Tax-authority capture | Issued invoices that capture an invoice number + CQT code |
| Webhook freshness | Provider webhooks reconcile invoice status without manual intervention |
4. Personas & Use Cases
| Persona | Goal in this feature |
|---|---|
| Owner | Connect a provider, configure issuance per invoice type, route channels, complete onboarding |
| Manager | Monitor issuance status and the audit trail; retry / adjust where allowed |
| Cashier | Trigger manual export for an order's invoice at the counter |
| System (payment) | Emit payment success that queues issuance automatically |
Core scenarios: an owner connects an iiapi provider with encrypted credentials and an explicit clientName → configures serial/category/policy per invoice type and routes each sale channel → a payment succeeds → the invoice is queued via Kafka/BullMQ, issued, and its number + CQT code captured → inbound webhooks reconcile status; tax-info lookup runs through the same iiapi seam.
5. User Stories
- As an owner, I want to connect an e-invoice provider per merchant with encrypted credentials and an explicit client name, so issuance is secure and unambiguous.
- As an owner, I want a guided onboarding flow to configure serial/category/policy per invoice type and route sale channels, so setup is correct without deep tax knowledge.
- As an owner, I want a completed payment to automatically queue and issue the invoice, so compliant invoices are produced without manual work.
- As a manager, I want issuance failures to retry per policy and be visible in an audit trail, so nothing is silently lost.
- As a cashier, I want to manually export an order's invoice at the counter, so I can issue on demand when needed.
- As an owner, I want a single provider seam to resolve seller tax info and issue invoices, so I configure one connection rather than two integrations.
6. Functional Requirements
| # | Requirement | URD ref |
|---|---|---|
| FR-1 | Create a merchant invoice profile linked to the seller tax identity | URD-CFG-001 |
| FR-2 | Connect a provider (VNPAY / VNIS via iiapi) per merchant via merchantId / link, with credentials stored encrypted and an explicit clientName required | URD-CFG-002 |
| FR-3 | Configure per invoice type the serial, category, tax method, and issuance policy | URD-CFG-003 |
| FR-4 | Route each sale channel to the provider config that should issue its invoices | URD-CFG-004 |
| FR-5 | Configure a retry policy (max retries + delay schedule) for failed issuance | URD-CFG-005 |
| FR-6 | Share an invoice profile across branches; guided onboarding wizard for provider setup | URD-CFG-006..007 |
| FR-7 | Queue an invoice for issuance automatically on a successful payment, via Kafka + BullMQ | URD-INV-001 |
| FR-8 | Issue the invoice via the provider and capture its number + tax-authority (CQT) code; track status (pending → processing → success / failed / cancelled) | URD-INV-002..003 |
| FR-9 | Retry a failed issuance per the configured policy; submit the issued invoice to the tax authority (CQT via T-VAN) when enabled | URD-INV-004..005 |
| FR-10 | Record an immutable audit-trail entry for every invoice event | URD-INV-006 |
| FR-11 | Adjust, replace, or cancel an issued invoice with a reason | URD-INV-007..008 |
| FR-12 | Process inbound provider webhooks (per-config webhook toggle) to update invoice status | URD-INV-009 |
Full requirement text and acceptance criteria live in the Tax & Invoice URD. This PRD references them rather than restating them.
7. Non-Functional Requirements
| Area | Requirement |
|---|---|
| Credential security | Provider credentials are stored encrypted at rest; no plaintext secrets |
| Data integrity | The invoice audit trail is append-only; status changes are recorded, never overwritten |
| Tenancy & authz | Provider connection and configs are scoped per merchant (x-merchant-id); credential changes are owner-gated |
| Resilience | Issuance is driven through Kafka + BullMQ queues with a configurable retry policy; failures are captured, not dropped |
| Idempotency | Webhook and queue processing tolerate redelivery without double-issuing |
| i18n | User-facing labels/statuses are bilingual ({ en, vi }) |
8. UX & Flows
Key screens (in apps/client): the iiapi-configs provider-connection management, the per-invoice-type serial/category/policy editor, channel routing, and the guided invoice onboarding flow.
9. Data & Domain
| Entity | Role |
|---|---|
MerchantInvoiceProfile | Merchant's invoicing setup, linked to the seller tax identity |
InvoiceProvider | The connected provider (iiapi) per merchant - vaults encrypted credentials, clientName |
InvoiceProviderConfig | Per invoice-type serial / category / policy and retry settings |
InvoiceConfigMapping | Routes a sale channel to the provider config that issues its invoices |
Invoice | The issued e-invoice - number, CQT code, status |
InvoiceAuditTracing | Immutable event trail for every invoice state change |
Conceptual only - full schema and invariants in the invoice domain model.
10. Dependencies & Assumptions
Depends on
- Seller tax identity (URD-TAX) - the invoice profile links to the merchant's MST, name, and address.
- Payment (
payment.success) - a successful payment is what queues issuance. - iiapi provider gateway (
@nx/iiapi) - issues the legal invoice and delivers webhooks. - T-VAN tax-authority network (
@nx/t-van) - CQT submission, validation, and the tax-info seam being migrated onto iiapi. - Kafka + BullMQ - the queue transport between payment success and issuance.
Assumptions
- The merchant has registered a seller tax identity (MST).
- The merchant holds valid provider credentials and a
clientNamefor the connection. - A sale channel is routed to an active provider config before issuance.
11. Risks & Open Questions
| Risk / question | Mitigation / status |
|---|---|
| Provider rejects or times out during issuance | Retry policy (max retries + delay) per config; failures recorded in the audit trail |
| Duplicate issuance on queue / webhook redelivery | Idempotent queue and webhook processing; status tracked per invoice |
| Credential leakage | Credentials stored encrypted; changes owner-gated |
| Single provider set (iiapi / T-VAN) | Accepted for this increment; multi-provider is a Non-Goal, deferred to P2 |
| T-VAN → iiapi tax-info migration regressions | Single seam validated against existing tax-info sync before cutover |
12. Release Plan & Launch Criteria
| Aspect | Plan |
|---|---|
| Phase | P1 (foundation) - see URD feature catalog for CFG and INV |
| Rollout | All merchants; enabled per merchant on provider connection |
| Migration | Tax-info resolution migrated from T-VAN to iiapi (single provider seam) |
| Launch criteria | Connect provider (encrypted creds + clientName) → configure per-type serial/policy → payment success queues + issues an invoice with number + CQT code → webhook reconciles status; tax-info lookup verified on iiapi |
| Monitoring | Issuance success/failure rate, retry depth, webhook reconciliation lag, queue backlog |
13. FAQ
Does issuing an invoice require manual action? No - by default a successful payment automatically queues and issues the invoice. Manual export at the counter and scheduled batch issuance are additional modes (URD-MOD).
Where are provider credentials stored? Encrypted at rest, scoped per merchant, with an explicit clientName - there is no implicit default client.
What happens if the provider rejects an issuance? It is retried per the configured policy (max retries + delay). If it still fails, the status is failed and the failure is recorded in the audit trail.
Why migrate tax-info from T-VAN to iiapi? So a single provider seam handles both seller tax-info lookup and invoice issuance - one connection to configure instead of two integrations.
Can I use a provider other than VNPAY / VNIS? Not in this increment - additional providers beyond the iiapi / T-VAN set are a Non-Goal, planned for P2.
References
- URD: Tax & Invoice - Invoice Lifecycle (INV) · Invoice Configuration (CFG)
- Builds on: Tax Identity (TAX) · Issuance Modes (MOD)
- Module: Tax & Invoice - overview + traceability
- Developer: @nx/invoice · domain model · iiapi · t-van