quickbooks-api
Version:
A modular TypeScript SDK for seamless integration with Intuit QuickBooks APIs. Provides robust authentication handling and future-ready foundation for accounting, payments, and commerce operations.
485 lines (404 loc) • 11.9 kB
text/typescript
// Imports
import { ApiClient } from '../../app';
import {
EmailStatus,
PhysicalAddress,
SalesItemLine,
GroupLine,
DescriptionOnlyLine,
DiscountLine,
SubTotalLine,
ReferenceType,
GlobalTaxCalculation,
EmailAddress,
ModificationMetadata,
DeliveryInfo,
PrintStatus,
LinkedTxn,
MemoRef,
TxnTaxDetail,
CustomField,
QuickbooksError,
} from '../types';
/**
* Invoice
*
* @description The Invoice Object
*/
export class Invoice {
/**
* @description The API client used to make requests to the API to manage the Customer object
*/
private apiClient: ApiClient;
// Setup the Required Properties
/**
* @description Transaction line items (required)
* @required
* @maxItems 750 when taxable
*/
public Line: Array<SalesItemLine | GroupLine | DescriptionOnlyLine | DiscountLine | SubTotalLine>;
/**
* @description Customer/job reference (required)
* @filterable
*/
public CustomerRef: ReferenceType;
/**
* @description Shipping origin address (required for automated tax)
* @minorVersion 35
*/
public ShipFromAddr: PhysicalAddress;
/**
* @description Currency reference (required if multicurrency enabled)
*/
public CurrencyRef: ReferenceType;
/**
* @description Tax calculation method (required for non-US companies)
* @allowedValues TaxExcluded, TaxInclusive, NotApplicable
*/
public GlobalTaxCalculation: GlobalTaxCalculation;
/**
* @description Project reference (required)
* @minorVersion 69
* @filterable
*/
public ProjectRef: ReferenceType;
/**
* @description Billing email (required if EmailStatus=NeedToSend)
*/
public BillEmail: EmailAddress;
// Setup the Readonly Properties
/**
* @description Unique identifier (read-only, system defined)
* @requiredForUpdate
* @filterable
* @sortable
*/
public readonly Id: string;
/**
* @description Version lock token (read-only, system defined)
* @requiredForUpdate
*/
public readonly SyncToken: string;
/**
* @description Modification metadata (read-only)
*/
public readonly MetaData: ModificationMetadata;
/**
* @description Home currency balance (read-only)
* @minorVersion 3
*/
public readonly HomeBalance: number;
/**
* @description Delivery information (read-only)
*/
public readonly DeliveryInfo: DeliveryInfo;
/**
* @description Total amount (read-only, system calculated)
*/
public readonly TotalAmt: number;
/**
* @description Sharable invoice link (read-only)
* @minorVersion 36
*/
public readonly InvoiceLink: string;
/**
* @description Recurring transaction reference (read-only)
* @minorVersion 52
*/
public readonly RecurDataRef: ReferenceType;
/**
* @description Tax exemption reference (read-only)
* @minorVersion 21
*/
public readonly TaxExemptionRef: ReferenceType;
/**
* @description Remaining balance (read-only)
* @filterable
* @sortable
*/
public readonly Balance: number;
/**
* @description Home currency total (read-only)
*/
public readonly HomeTotalAmt: number;
// Setup the Optional Properties
/**
* @description Transaction date (yyyy/MM/dd)
* @filterable
* @sortable
*/
public TxnDate?: Date;
/**
* @description Goods delivery date
*/
public ShipDate?: Date;
/**
* @description Shipping tracking number
*/
public TrackingNum?: string;
/**
* @description Printing status
* @allowedValues NotSet, NeedToPrint, PrintComplete
*/
public PrintStatus?: PrintStatus;
/**
* @description Sales terms reference
* @filterable
*/
public SalesTermRef?: ReferenceType;
/**
* @description Transaction source (internal use)
*/
public TxnSource?: string;
/**
* @description Related transactions
* @maxItems 750
*/
public LinkedTxn?: Array<LinkedTxn>;
/**
* @description Deposit account reference
*/
public DepositToAccountRef?: ReferenceType;
/**
* @description Allow online ACH payments
*/
public AllowOnlineACHPayment?: boolean;
/**
* @description Transaction location type
* @minorVersion 4
*/
public TransactionLocationType?: string;
/**
* @description Payment due date
* @filterable
* @sortable
*/
public DueDate?: Date;
/**
* @description Private transaction note (max 4000 chars)
*/
public PrivateNote?: string;
/**
* @description CC email address
* @minorVersion 8
*/
public BillEmailCc?: EmailAddress;
/**
* @description Customer-facing memo
*/
public CustomerMemo?: MemoRef;
/**
* @description Email status
* @allowedValues NotSet, NeedToSend, EmailSent
*/
public EmailStatus?: EmailStatus;
/**
* @description Currency exchange rate
*/
public ExchangeRate?: number;
/**
* @description Deposit amount
*/
public Deposit?: number;
/**
* @description Tax details
*/
public TxnTaxDetail?: TxnTaxDetail;
/**
* @description Allow credit card payments
*/
public AllowOnlineCreditCardPayment?: boolean;
/**
* @description Custom fields
*/
public CustomField?: CustomField;
/**
* @description Shipping address
*/
public ShipAddr?: PhysicalAddress;
/**
* @description Department reference
*/
public DepartmentRef?: ReferenceType;
/**
* @description BCC email address
* @minorVersion 8
*/
public BillEmailBcc?: EmailAddress;
/**
* @description Shipping method reference
*/
public ShipMethodRef?: ReferenceType;
/**
* @description Billing address
*/
public BillAddr?: PhysicalAddress;
/**
* @description Tax calculation order
* @description true = apply discount before tax
*/
public ApplyTaxAfterDiscount?: Boolean;
/**
* @description The constructor for the Invoice object
* @param apiClient The API client used to make requests to the API
* @param invoiceCreationData The data used to create the invoice
*/
constructor(apiClient: ApiClient, invoiceCreationData: InvoiceCreationData) {
// Set the API Client
this.apiClient = apiClient;
// Set the Creation Data
this.Line = invoiceCreationData?.Line ?? new Array();
this.CustomerRef = invoiceCreationData?.CustomerRef ?? null!;
this.ShipFromAddr = invoiceCreationData?.ShipFromAddr ?? null!;
this.CurrencyRef = invoiceCreationData?.CurrencyRef ?? null!;
this.GlobalTaxCalculation = invoiceCreationData?.GlobalTaxCalculation ?? null!;
this.ProjectRef = invoiceCreationData?.ProjectRef ?? null!;
this.BillEmail = invoiceCreationData?.BillEmail ?? null!;
// Set the Readonly Properties
this.Id = null!;
this.SyncToken = null!;
this.MetaData = null!;
this.HomeBalance = null!;
this.DeliveryInfo = null!;
this.TotalAmt = null!;
this.InvoiceLink = null!;
this.RecurDataRef = null!;
this.TaxExemptionRef = null!;
this.Balance = null!;
this.HomeTotalAmt = null!;
}
/**
* @description Set the API Client
* @param apiClient - The API client
*/
public setApiClient(apiClient: ApiClient) {
this.apiClient = apiClient;
}
/**
* @description Reload the Invoice Data
* @throws {QuickbooksError} If the Invoice was not found
*/
public async reload() {
// Get the Invoice by ID
const invoice = await this.apiClient.invoices.getInvoiceById(this.Id);
// Check if the Invoice was not Found
if (!invoice) throw new QuickbooksError('Invoice not found', await ApiClient.getIntuitErrorDetails(null));
// Assign the Properties
Object.assign(this, invoice);
}
/**
* @description Custom JSON serialization to exclude private properties
*/
private toJSON() {
// Setup the Excluded Properties
const excludedProperties = ['apiClient'];
// Setup the JSON Object
const jsonData = { ...Object.fromEntries(Object.entries(this).filter(([key]) => !excludedProperties.includes(key))) };
// Return the JSON Object
return jsonData;
}
/**
* @description Updates or creates (if the Id is not set) the Invoice
*/
public async save() {
// Get the Invoice URL
const url = await this.apiClient.invoices.getUrl();
// Setup the Request Data
const requestData: RequestInit = {
method: 'POST',
body: JSON.stringify({ ...this.toJSON(), sparse: true }),
};
// Update the Invoice
const { responseData } = await this.apiClient.runRequest(url.href, requestData);
// Extract the Invoice from the response (QuickBooks returns { Invoice: {...} } or wrapped format)
const invoiceData = responseData?.Invoice?.[0] || responseData?.Invoice || responseData;
// Assign the Properties
Object.assign(this, invoiceData);
}
/**
* @description Sends the Invoice via email
* @throws {QuickbooksError} If the Invoice ID is not set or the send fails
*/
public async send() {
// Check if the Invoice has an ID
if (!this.Id) throw new QuickbooksError('Invoice must be saved before sending', await ApiClient.getIntuitErrorDetails(null));
// Get the Invoice URL and append /send
const url = await this.apiClient.invoices.getUrl();
url.pathname = `${url.pathname}/${this.Id}/send`;
// Setup the Request Data
const requestData: RequestInit = {
method: 'POST',
body: JSON.stringify({ ...this.toJSON(), sparse: true }),
};
// Send the Invoice
const { responseData } = await this.apiClient.runRequest(url.href, requestData);
// Extract the Invoice from the response
const invoiceData = responseData?.Invoice?.[0] || responseData?.Invoice || responseData;
// Assign the Properties
if (invoiceData) Object.assign(this, invoiceData);
}
/**
* @description Downloads the Invoice as a PDF
* @returns {Promise<Blob>} The PDF file as a Blob
* @throws {QuickbooksError} If the Invoice ID is not set or the download fails
*/
public async downloadPDF(): Promise<Blob> {
// Check if the Invoice has an ID
if (!this.Id) throw new QuickbooksError('Invoice must be saved before downloading PDF', await ApiClient.getIntuitErrorDetails(null));
// Get the Invoice URL and append /pdf
const url = await this.apiClient.invoices.getUrl();
url.pathname = `${url.pathname}/${this.Id}/pdf`;
// Get the Token
const token = await this.apiClient.authProvider.getToken();
// Setup the Request Data for PDF
const requestData: RequestInit = {
method: 'GET',
headers: {
Accept: 'application/pdf',
Authorization: `Bearer ${token.accessToken}`,
},
};
// Download the PDF
const response = await fetch(url.href, requestData);
// Check if the Response has failed
if (!response.ok) {
const errorDetails = await ApiClient.getIntuitErrorDetails(response);
throw new QuickbooksError('Failed to download PDF', errorDetails);
}
// Return the PDF as a Blob
return await response.blob();
}
/**
* @description Voids the Invoice
* @throws {QuickbooksError} If the Invoice ID is not set or the void fails
*/
public async void() {
// Check if the Invoice has an ID
if (!this.Id) throw new QuickbooksError('Invoice must be saved before voiding', await ApiClient.getIntuitErrorDetails(null));
// Get the Invoice URL and append operation=void
const url = await this.apiClient.invoices.getUrl();
url.pathname = `${url.pathname}/${this.Id}`;
url.searchParams.set('operation', 'void');
// Setup the Request Data
const requestData: RequestInit = {
method: 'POST',
body: JSON.stringify({ ...this.toJSON(), sparse: true }),
};
// Void the Invoice
const { responseData } = await this.apiClient.runRequest(url.href, requestData);
// Extract the Invoice from the response
const invoiceData = responseData?.Invoice?.[0] || responseData?.Invoice || responseData;
// Assign the Properties
if (invoiceData) Object.assign(this, invoiceData);
}
}
// Setup the Creation Data
export type InvoiceCreationData = {
Line: Array<SalesItemLine | GroupLine | DescriptionOnlyLine | DiscountLine | SubTotalLine>;
CustomerRef: ReferenceType;
ShipFromAddr: PhysicalAddress;
CurrencyRef: ReferenceType;
GlobalTaxCalculation: GlobalTaxCalculation;
ProjectRef: ReferenceType;
BillEmail: EmailAddress;
};