Key Takeaway
Every taxable entrepreneur in Indonesia, a PKP, must issue a Faktur Pajak, the legal tax invoice recognised by the Directorate General of Taxes. An ERP that generates sales invoices therefore has to produce e-Faktur data that reconciles to them exactly, because the two are checked against each other.
Every taxable entrepreneur, known as PKP, that sells goods or services in Indonesia has to issue a Faktur Pajak, the legal tax invoice recognized by the Directorate General of Taxes, or DJP. If you are building or extending an ERP for an Indonesian business, the tax invoice module is not optional polish, it is the piece that keeps the whole system compliant, because a badly formed invoice number or a miscalculated PPN line can trigger penalties during an audit.
This post walks through the four pieces that actually matter when you wire tax invoicing into a custom ERP: validating the taxpayer identification number, calculating PPN correctly on the taxable base, generating invoice numbers that DJP will accept, and producing an export file the tax system can ingest. The underlying government system moved from the old e-Faktur desktop client to the unified Coretax platform, and the article covers both, because plenty of ERPs still support legacy data alongside the new flow.
NPWP and the taxpayer identity problem
NPWP, short for Nomor Pokok Wajib Pajak, is the taxpayer identification number DJP assigns to every registered business and individual. From 1 July 2024, under PENG-26/PJ.09/2024, DJP's services began using the 16 digit NIK, the national identity number printed on the KTP card, as the NPWP for resident individual taxpayers, while companies, government bodies, and non-resident taxpayers keep a separate 16 digit NPWP. Other parties could keep accepting the older 15 digit format, written as 00.000.000.0-000.000, only until 31 December 2024 if their systems were not ready, and Coretax, live since 1 January 2025, works with the 16 digit forms, so any ERP that still stores or validates that dashed format will silently accept invalid customer records.
- Store the taxpayer id as a normalized 16 digit string, not the dashed display format, and generate the dashed version only for printing.
- Validate length and digit-only content at data entry time, because a truncated or padded id will fail Coretax validation later in the invoice lifecycle, not at save time.
- Keep a separate flag for whether a customer record uses NIK-as-NPWP or a standalone company NPWP, since the two lookup paths differ when you reconcile against DJP records.
Run a nightly reconciliation job that flags any customer or vendor master record whose taxpayer id is not exactly 16 numeric digits. Catching this before an invoice is created is far cheaper than fixing a rejected submission after the fact.
Calculating PPN on the right base
PPN, Indonesia's value added tax, is calculated on the Dasar Pengenaan Pajak, or DPP, which is the taxable base, not always the full invoice subtotal. April 2022 raised the rate from 10 percent to 11 percent for most goods and services, and from 1 January 2025 the rate is 12 percent, but PMK 131/2024 applies it to a DPP Nilai Lain of 11/12 of the price for everything except luxury goods subject to PPnBM, so the effective amount collected on standard transactions still lands at 11 percent. That distinction between the statutory rate printed in the law and the effective rate applied to the DPP is exactly the kind of detail a tax engine has to encode explicitly, because a naive implementation that multiplies the subtotal by whatever the current headline rate is will overcharge every customer the moment the statutory number changes again. Your ERP's tax engine needs to compute the DPP first, applying any discounts, then the DPP Nilai Lain where it applies, then the rate, and round DPP and PPN to whole rupiah as PER-11/PJ/2025 Article 129 requires: amounts below 0.50 round down and 0.50 or more round up.
- Compute DPP after line-level discounts but before any withholding tax, since PPN and PPh are calculated independently on different bases.
- Round DPP and PPN to whole rupiah, rounding below 0.50 down and 0.50 or more up as PER-11/PJ/2025 requires, and store the rounding remainder so finance can trace variance during reconciliation.
- Make the rate itself a configurable parameter tied to an effective date range, because Indonesian VAT rates have changed more than once and will likely change again.
// Normalize a taxpayer id before it ever reaches an invoice row.
// From 1 Jul 2024 (PENG-26/PJ.09/2024) DJP services began using the 16-digit
// NIK as NPWP for resident individuals; companies and non-residents have a
// separate 16-digit NPWP.
function normalizeTaxId(raw: string): { digits: string; valid: boolean } {
const digits = raw.replace(/\D/g, "")
// Other parties could accept the 15-digit form only until 31 Dec 2024
const valid = digits.length === 16
return { digits, valid }
}
// PPN under PMK 131/2024 (from 1 Jan 2025): 12% of a DPP Nilai Lain of 11/12
// of the price for non-luxury goods, 12% of the full price for PPnBM goods.
// PER-11/PJ/2025 Art. 129: DPP and PPN in whole rupiah, below 0.50 rounds
// down and 0.50 or more rounds up (Math.round does this for positive amounts).
function calcPpn(priceRupiah: number, isLuxury = false, ratePercent = 12): {
dpp: number
ppn: number
total: number
} {
const price = Math.round(priceRupiah)
const dpp = isLuxury ? price : Math.round((price * 11) / 12)
const ppn = Math.round((dpp * ratePercent) / 100)
return { dpp, ppn, total: price + ppn }
}Why hardcoding the rate as a constant will hurt you
It's tempting to hardcode PPN as 0.11 somewhere deep in a pricing service and move on. The problem shows up the moment finance needs to reprocess an old invoice, run a what-if simulation for a future rate change, or handle a transitional period where old and new rates coexist for goods shipped versus invoiced on different dates. Treat the rate as versioned reference data with an effective_from date, resolved at the moment the invoice DPP is calculated, not at the moment the sales order was created.
Never calculate PPN from a customer-facing display price that already includes rounding artifacts from currency formatting. Always calculate off the underlying numeric DPP stored at full precision, then round only for display and for the final tax line, or you will accumulate drift across thousands of invoices.
NSFP: the invoice number DJP actually controls
NSFP, Nomor Seri Faktur Pajak, is the fiscal invoice serial number, and it is the one part of the invoice your ERP does not get to invent freely. Under the legacy e-Faktur system, a PKP requested blocks of serial numbers through the e-Nofa application and then assigned them sequentially inside the desktop client, which meant your ERP had to track a local pool of pre-allocated numbers and warn finance before the pool ran dry. Under Coretax, the platform DJP put into service on 1 January 2025, that manual allocation step disappears entirely: the NSFP is assigned automatically when the e-Faktur is uploaded and approved by DJP, and the format expanded from 16 to 17 digits, with older legacy-format numbers getting a 9 inserted automatically at the fifth digit when that data flows into Coretax. If your ERP still writes invoices through the desktop client for a transitional period, expect the number your system displays locally to differ from the number Coretax ultimately records, and reconcile against the platform's version, not your own.
| Field | Legacy e-Faktur | Coretax |
|---|---|---|
| Serial length | 16 digits | 17 digits |
| Allocation method | Requested in blocks via e-Nofa | Auto-assigned when DJP approves the upload |
| Submission format | CSV import into the desktop client | XML import built to DJP's templates, portal entry or a PJAP link |
| Correction path | Manual replacement invoice | In-platform revision or cancellation |
Building the export DJP expects
Regardless of which generation of the system you target, the export is structured data describing header fields, taxpayer identities on both sides of the transaction, and line items with their own DPP and PPN. Treat the export as a strict contract, not a best-effort dump: DJP's XML templates add data validation for fields such as NPWP and NITKU, and data that fails validation is not accepted, so a malformed field is far cheaper to catch before the file leaves your system than after.
- Generate the export from a dedicated read model built at invoice-finalization time, not from live transactional tables, so a later edit to an order cannot silently change an already-submitted invoice.
- Validate every taxpayer id, date format, and numeric field against the target schema before the file leaves your system, and fail loudly rather than submitting a partially valid batch.
- Persist the exact submitted payload alongside the invoice record, so a DJP rejection or an audit years later can be reproduced from what was actually sent, not reconstructed from current data.
- Log the DJP response, including the assigned NSFP and any validation error codes, back onto the invoice record so accounting has a single place to check status.
Separating invoice finalization from invoice submission, with an explicit status field in between, is the single change that most reduces tax-invoice incidents in production ERPs. It gives finance a checkpoint to catch mistakes before anything reaches DJP.
Checklist before you ship
A few final things worth verifying before this module goes live for a real PKP business, since fixing any of these after the first production invoice batch is considerably more painful than catching them in code review.
- Confirm the invoice can represent both taxable and tax-exempt line items on the same document, since many real invoices mix the two.
- Confirm cancellation and correction flows produce a new, traceable serial number rather than silently mutating a submitted invoice.
- Confirm your export format is versioned so a future DJP schema change does not require rewriting the whole integration from scratch.
Sources
- Direktorat Jenderal Pajak — e-Faktur Client Desktop tax invoice creation guide
- Wikipedia — Directorate General of Taxes (Indonesia)
- Wikipedia (Indonesian) — Nomor Pokok Wajib Pajak (NPWP)
- Wikipedia — Value-added tax
- Direktorat Jenderal Pajak — KT-02/2025: implementation of Coretax DJP from 1 January 2025
- Direktorat Jenderal Pajak — PENG-26/PJ.09/2024 on using the 16 digit NPWP and NITKU
- Direktorat Jenderal Pajak — PER-11/PJ/2025 (Articles 37, 43 and 129: invoice number, serial assignment and rounding)
- Direktorat Jenderal Pajak — Template XML dan Converter Excel ke XML for Coretax imports

