Milesoft QuickBooks Extension (quickbooks-ext)

1. Overview & Purpose

The QuickBooks Extension (quickbooks-ext) is a robust, production-grade integration wrapper around the official Intuit QuickBooks Online (QBO) Java SDK designed specifically for the Milesoft ecosystem. It simplifies secure, type-safe connections to the QBO v3 Services API while ensuring architectural and transactional consistency across downstream applications.

Key Benefits & Core Capabilities:

  • Type-Safe Domain Abstraction: Bridges the complex, verbose, and nested types of the official Intuit SDK into clean, immutable, and domain-specific Java structures (using records and Lombok builder-equipped classes).
  • Comprehensive Entity Sync: Unified API interfaces to list, filter, page, and write standard financial objects including Business (CompanyInfo), Reference (Customers, Classes, Departments), ItemDefinition, AccountDefinition, Invoice, Payment, Deposit, and Purchase.
  • Change Data Capture (CDC): Built-in support for Intuit's delta-query engine via getCdc(), tracking only updated or deleted records across multiple entities since a specific timestamp.
  • Idempotent Connection Setup: Dynamically initializes thread-safe DataService connections using ephemeral OAuth2 access tokens and handles sandbox/production configurations transparently.
  • Automatic Services Initialization: Programmatic intelligence to automatically query, resolve, or lazily create dependent structures—such as the default Services income account required for service-item mapping.

2. Installation & Dependency Configuration

The QuickBooks Extension is hosted securely inside our Google Cloud Artifact Registry repository.

To configure your application to resolve and pull this dependency:

2.1 Gradle Setup (build.gradle)

Apply the Google Cloud Artifact Registry plugin and include the repository reference:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
    id "java"
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
    }
}

dependencies {
    // Milesoft Commons foundation (required by quickbooks-ext)
    implementation "io.milesoft:commons:2.0.1"
    
    // QuickBooks Integration Extension
    implementation "io.milesoft:quickbooks-ext:2.0.1"
}

3. Configuring QuickBooks in Spring Boot

To enable proper dependency injection of the QuickbooksService within a downstream Spring Boot microservice, define it as a managed @Bean in your configuration classes.

Since QuickbooksService only requires a standard java.time.Clock instance to resolve transaction times, instantiation is extremely lightweight and non-blocking:

3.1 Bean Configuration

import io.milesoft.quickbooks.services.QuickbooksService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Clock;

@Configuration
public class QuickbooksConfig {

    @Bean
    public QuickbooksService quickbooksService(Clock clock) {
        return new QuickbooksService(clock);
    }
}

4. Core Domain Models & Data Types

The library defines a strict, type-safe representation of financial entities.

4.1 Account Classification (Classification)

A specialized Enum representing the financial category of an account, mapped dynamically to QBO's internal classification schemes.

public enum Classification {
    Asset,       // Stuff the company owns (Cash, trucks, land)
    Equity,      // Owner's stake or cash injection (Owner Deposit)
    Expense,     // Money spent on projects (Materials, labor, subcontractor fees)
    Liability,   // Money the company owes (Loans, credit lines)
    Revenue,     // Money earned from projects (Progress billings, client payments)
    Other;       // Fallback classification

    public boolean isIncome() {
        return this == Asset || this == Equity || this == Revenue;
    }

    public boolean isExpense() {
        return !isIncome();
    }
}

4.2 Core Reference Record (Reference)

A lightweight, general-purpose record mapping identifier details of QBO taxonomy (such as customer records, classes, and departments).

import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;

public record Reference(
    String id, 
    String name, 
    @Nullable String type, 
    boolean deleted, 
    @Nullable ZonedDateTime updated
) {
    public Reference(String id, String name) {
        this(id, name, null, false, null);
    }
}

4.3 Immutable Business Details (Business)

Lombok builder-equipped model capturing company profile, accounting terminology overrides, and data origin properties.

import java.time.ZonedDateTime;
import lombok.Builder;
import lombok.Value;

@Value
@Builder(toBuilder = true)
public class Business {
    String companyName;
    String legalName;
    String email;
    String phone;
    String departmentLabel;  // Overridden terminology configured in QuickBooks
    String customerLabel;    // Overridden terminology configured in QuickBooks
    String quickbooks;       // QuickBooks Domain (e.g. QBO)
    ZonedDateTime updated;
}

4.4 Item Definitions & Instances

  • ItemDefinition (Record): Models the abstract blueprint of a catalog category or service item.
  • ItemInstance (Record): Models the actual line item reference bound to transactions (Invoices, Purchases), carrying quantity, price, totals, taxability flags, classification, and descriptions.
public record ItemDefinition(
    String id, 
    String name, 
    boolean category, 
    boolean deleted, 
    @Nullable ZonedDateTime updated
) {
    public Reference toReference() {
        return new Reference(id, name, "Item", deleted, updated);
    }
}

public record ItemInstance(
    Reference definition, 
    BigDecimal quantity, 
    BigDecimal unitPrice, 
    BigDecimal totalPrice, 
    boolean taxable,
    @Nullable Reference clazz, 
    @Nullable String description
) {}

5. API Reference Guide

The orchestrating entry point is io.milesoft.quickbooks.services.QuickbooksService. Every operations API requires realmId (QuickBooks Company ID) and a valid accessToken dynamically resolved through OAuth workflows.

5.1 Query & Pagination Read Operations

All get-methods support dynamic cursors, standard boundaries (10 to 1000), and a delta timestamp since.

Method Signatures Return Type Description
getCompanyInfo(realmId, accessToken) Business Fetches legal settings, company properties, and localization terminologies.
getCustomers(realmId, accessToken, cursor, limit, since) PagedResults<Reference> Retrieves paginated QBO Customer entities.
getClasses(realmId, accessToken, cursor, limit, since) PagedResults<Reference> Retrieves paginated QBO Class tags (cost centers).
getDepartments(realmId, accessToken, cursor, limit, since) PagedResults<Reference> Retrieves paginated QBO Departments (internal branches).
getItems(realmId, accessToken, cursor, limit, since) PagedResults<ItemDefinition> Retrieves product and service definitions.
getAccounts(realmId, accessToken, cursor, limit, since) PagedResults<AccountDefinition> Retrieves Chart of Accounts (COA) entities.
getInvoices(realmId, accessToken, cursor, limit, since) PagedResults<Invoice> Queries customer invoices and outstanding balances.
getPayments(realmId, accessToken, cursor, limit, since) PagedResults<Payment> Queries cash receipts applied to customers or invoices.
getDeposits(realmId, accessToken, cursor, limit, since) PagedResults<Deposit> Queries bank deposit transactions and distribution lines.
getPurchases(realmId, accessToken, cursor, limit, since) PagedResults<Purchase> Queries cash, check, or credit card expenses.
getCdc(realmId, accessToken, entityNames, since) CdcResults Performs Change Data Capture across standard entity tables.

5.2 Creation & Write Operations

Write-operations immediately commit the state to QuickBooks Online and return transformed, hydrated domain responses.

Method Signatures Return Type Description
addCustomer(realmId, accessToken, name) Reference Provisions a new active Customer.
addClass(realmId, accessToken, name) Reference Provisions a new Class tag.
addDepartment(realmId, accessToken, name) Reference Provisions a new Department.
addCategoryItem(realmId, accessToken, name) ItemDefinition Creates a categorization folder/group item.
addServiceItem(realmId, accessToken, name) ItemDefinition Creates an active service catalog item, resolving or creating the Services income account dynamically as needed.
addSourceAccount(realmId, accessToken, name, classification) AccountDefinition Provisions a standard Income Account.
addDestinationAccount(realmId, accessToken, name, classification) AccountDefinition Provisions a standard Expense Account.
addInvoice(realmId, accessToken, customer, items, dueAt) Invoice Generates a new transaction invoice linked to the specified customer and line items.
addPayment(realmId, accessToken, customer, invoice, amount, earnedAt) Payment Records customer payment, optionally linking it to an unpaid invoice transaction.
addDeposit(realmId, accessToken, account, lines, depositedAt) Deposit Captures deposit details distributed among custom transaction lines.
addPurchase(realmId, accessToken, account, expenseAccount, payee, clazz, department, amount, paymentType, incurredAt) Purchase Creates an expense payment transaction distributed into a designated expense/class bucket.

6. Real-World Integration Patterns

In downstream applications, the raw extension client is typically wrapped inside an application-level manager. This wrapper handles integration routing, secure dynamic token caching, and automated token-expiration retries.

Here is a simplified example of how to wrap and utilize quickbooks-ext inside your application logic:

import io.milesoft.quickbooks.domain.*;
import io.milesoft.quickbooks.exceptions.QuickbooksException;
import io.milesoft.quickbooks.services.QuickbooksService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.function.Function;

@Slf4j
@Service
public class QuickBooksSyncManager {

    private final QuickbooksService wrappedSdkClient;
    private final OAuthTokenService oauthService; // Your service that manages access/refresh tokens

    public QuickBooksSyncManager(QuickbooksService wrappedSdkClient, OAuthTokenService oauthService) {
        this.wrappedSdkClient = wrappedSdkClient;
        this.oauthService = oauthService;
    }

    /**
     * Executes QBO SDK commands with automatic authentication retry and token refreshing.
     */
    private <R> R executeWithRetry(String companyId, Function<String, R> operation) {
        String accessToken = oauthService.getAccessToken(companyId);
        try {
            return operation.apply(accessToken);
        } catch (QuickbooksException e) {
            // Check if the failure is due to expired authorization tokens
            if (e.isAuthentication()) {
                log.info("OAuth2 token expired for company {}. Re-authenticating...", companyId);
                String refreshedToken = oauthService.refreshTokens(companyId);
                return operation.apply(refreshedToken);
            }
            throw e;
        }
    }

    public Business getCompanySettings(String companyId) {
        return executeWithRetry(companyId, token -> 
            wrappedSdkClient.getCompanyInfo(companyId, token)
        );
    }

    public PagedResults<Reference> fetchUpdatedCustomers(String companyId, long cursor, ZonedDateTime since) {
        return executeWithRetry(companyId, token -> 
            wrappedSdkClient.getCustomers(companyId, token, cursor, 50, since)
        );
    }

    public Invoice createClientInvoice(String companyId, Reference customer, List<ItemInstance> lines) {
        return executeWithRetry(companyId, token -> 
            wrappedSdkClient.addInvoice(companyId, token, customer, lines, ZonedDateTime.now().plusDays(30))
        );
    }
}