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, andPurchase. - 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
DataServiceconnections 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
Servicesincome 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))
);
}
}