Vertical · SaaS subscription billing
QuickBooks / Xero → NetSuite · ARR-stage operators

NetSuite for SaaS subscription billing.

ARR has crossed QuickBooks' ceiling and Xero's ceiling alike. ASC 606 deferral allocations are being maintained in a spreadsheet. Multi-entity and multi-currency have arrived together. Below: the four capabilities the GAAP close actually demands from a SaaS operator's GL, plus a short SuiteScript extension for usage-based or hybrid billing.

Capability · deferral

Deferred revenue waterfall.

A native ASC 606 framework that produces a deferred revenue liability at contract signing, allocates it across performance obligations by SSP, and unbundles recognition journal entries on schedule. The waterfall is auditable row by row.

Step 01

Contract signing

The contract line is created on the customer record. A deferred revenue liability posts for the full contract value, split by the SSP allocation schedule.

Step 02

Performance obligations

The SSP allocation under ASC 606 produces one performance obligation per distinct good or service — implementation, base subscription, premium tier, committed usage bundle. Each has its own schedule.

Step 03

Recognition journal entries

A scheduled SuiteScript unbundles the deferred liability each month on the recognition date. The CFO sees the JE — the controller sees the audit trail — the auditor sees the saved-search XML.

The SSP schedule, the allocation logic, and the recognition script all live in your repo. The auditor reads the same artifacts the controller reads — there is no parallel spreadsheet to keep in sync.

Capability · metrics

MRR / ARR-native reporting.

Saved searches pinned to the contract line — not to a Stripe webhook, not to an exported CSV. The four views below are the ones the SaaS board expects in the deck, drafted from a single source of truth.

Saved search 01

MRR by cohort

Booked MRR sliced by signup month, with expansion, contraction, and churn columns — booked through a saved search that the controller can audit on a Tuesday morning.

Saved search 02

Booked ARR

Annualized booked ARR by entity, by currency, and by sales region — pinned to the contract line, not to a Stripe webhook. Runs against the same data the invoice posts from.

Saved search 03

Net new ARR

New, expansion, contraction, and churn all in one scheduled workbook — pushed to Slack on the first business day of every month with the prior-month delta inline.

Saved search 04

GRR / NRR

Gross retention and net retention by cohort. The view the SaaS board expects in the deck, drafted from the saved-search XML, not from a hand-exported CSV into Excel.

Capability · multi-currency

Multi-currency subscription billing.

A EUR-denominated customer, a USD functional-currency parent, a GBP bank settlement, and a SGD intercompany receivable. OneWorld handles the FX lock, the intercompany JE, and the consolidated elimination without a foreign-currency spreadsheet in sight.

  • Step 01

    Customer pays in currency X

    A European customer signs a EUR 90,000 annual contract. The invoice posts in the selling subsidiary in EUR; an intercompany receivable opens against the US operating entity at the contracted FX rate.

  • Step 02

    FX rate lock

    The rate is locked at contract signing — not at month-end, not at cash receipt. The PnL impact of FX movement is visible, predictable, and isolated from operational variance.

  • Step 03

    Books in functional currency

    The US entity books the contract value in USD at the locked rate. The intercompany is eliminated at consolidation. The deferred revenue waterfall runs entirely in functional currency.

  • Step 04

    Consolidated elimination

    OneWorld consolidation produces a single ARR figure by cohort, by currency, by region. The close runs in hours, not in days — and the controller does not own a foreign-currency spreadsheet.

Code · extension

A SuiteScript extension.

Below: a UserEvent that reconciles usage events against the contract line on invoice creation — the piece that closes the gap between a flat-rate subscription and a usage-based or hybrid-priced contract.

code · sample

suitescript · 2.1 · user-event

/**
 * @NApiVersion 2.1
 * @NScriptType UserEventScript
 * @NModuleScope SameAccount
 *
 * Reconciles usage events against the contract line on invoice creation.
 * Booking model: hybrid — flat base + committed usage + overage.
 * Engagement 07 — delivery #4 of #9 (see SOW §2.1).
 */

define(['N/record', 'N/search', 'N/log', 'N/runtime'], (record, search, log, runtime) => {
  const USAGE_LEDGER = 'customrecord_usage_event';
  const USAGE_INCLUDE = 'customsearch_usage_open_period';

  const aggregateUsage = (contractId, periodStart, periodEnd) => {
    const usage = { metered: 0, committed: 0, overage: 0 };
    search.create({
      type: USAGE_LEDGER,
      filters: [
        ['custrecord_usage_contract', 'is', contractId],
        'AND',
        ['custrecord_usage_period_start', 'within', periodStart, periodEnd],
      ],
      columns: [
        { name: 'custrecord_usage_metered', summary: 'SUM' },
        { name: 'custrecord_usage_committed', summary: 'SUM' },
      ],
    }).run().each((row) => {
      usage.metered = Number(row.getValue({ name: 'custrecord_usage_metered', summary: 'SUM' }));
      usage.committed = Number(row.getValue({ name: 'custrecord_usage_committed', summary: 'SUM' }));
      return true;
    });
    usage.overage = Math.max(0, usage.metered - usage.committed);
    return usage;
  };

  const beforeSubmit = (ctx) => {
    if (ctx.type !== ctx.UserEventType.CREATE) return;
    const inv = ctx.newRecord;
    const contractId = inv.getValue('custbody_subscription_contract');
    if (!contractId) return;

    const usage = aggregateUsage(
      contractId,
      inv.getValue('custbody_period_start'),
      inv.getValue('custbody_period_end'),
    );
    inv.setValue('custbody_usage_metered', usage.metered);
    inv.setValue('custbody_usage_overage', usage.overage);
    log.audit('usage:reconciled', { contractId, usage });
  };

  return { beforeSubmit };
});

scripts/billing/usage-reconcile.ts from engagement 07 — peer-reviewed, deployable, in your repo. The overage line posts to the same deferred revenue waterfall as the flat-rate base, so ASC 606 allocations stay coherent across both pricing models.

Next step

Book a discovery call.

Send a four-field note — ARR, current ERP, currency footprint, target close date — and we'll send a Calendly link back to your inbox inside a minute. If there is a fit, the next step is a signed scope; if there isn't, we'll tell you that on the same call.