QuickBooks Integration with AWS Lambda (Node.js)

Introduction

QuickBooks is an accounting and financial management software developed by Intuit. It helps businesses handle invoicing, payments, payroll, and tax calculations.

Quickbooks operates in mainly 2 domains that is Accounting and Payments. Both accounting and Payments have different SDKs and APIs based on the language we can choose which method is more feasible for us

Prerequisites

1. QuickBooks Developer Account

  • Sign up at QuickBooks Developer Portal
  • Create an app in QuickBooks and get the following credentials:
    • CLIENT_ID
    • CLIENT_SECRET
    • REDIRECT_URI
    • REALM_ID
    • ACCESS_TOKEN (obtained via OAuth 2.0 flow)
    • Refresh Token (obtained via playground of QuickBooks Developer portal)

Installation

1. Install Dependencies

npm install node-quickbooks dotenv axios

2. Authentication Flow (OAuth 2.0)

  1. Get Authorization Code
  • Redirect the user to QuickBooks authorization URL:
https://appcenter.intuit.com/connect/oauth2
  • Use scope: com.intuit.quickbooks.payment and com.intuit.quickbooks.accounting
  1. Exchange Code for Access Token
  • Use client_id, client_secret, and authorization_code to obtain an access_token and refresh_token.
  1. Refresh Token Automatically
  • Use the refresh_token to get a new access_token before it expires.

Implementations Details

QuickBooks provides two methods for integrating QuickBooks Payments and QuickBooks Accounting: QuickBooks SDK and QuickBooks Online API. In this blog, we integrated only QuickBooks Accounting using the SDK, which does not require any frontend integration. However, when initiating a payment, front-end integration is necessary.

We obtained the Access Token and Refresh Token from the QuickBooks playground and stored them in the database. To ensure continued access, we use the refresh token to acquire a new access token whenever needed. Additionally, a cron job runs every 99 days to regenerate the refresh token, preventing it from expiring.


API Endpoints

1. Generate Access token using the refresh token

module.exports.refreshAccessTokenAPI = async (refreshToken) => {
    // Create the request body for the refresh request
    const refreshData = querystring.stringify({
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
    })

    // Make the request to refresh the token
    const response = await axi.post(TOKEN_URL, refreshData, {
        // Auth headers for the request
        auth: {
            username: clientId,
            password: clientSecret,
        },
        // Request headers
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
    })

    // Return the token response object
    return response.data
}

2. Create customer in the QuickBooks

const createCustomer = async (qbo, customerData) => {
    console.log('entering createCustomer')

    return new Promise((resolve, reject) => {
        qbo.createCustomer(customerData, async (err, customer) => {
            if (err) {
                console.error('Error creating customer:', JSON.stringify(err))

                if (err.Fault && err.Fault.type === 'ValidationFault') {
                    console.log('validation entering here ')
                    reject(new Error({ success_status: false, message: err.Fault.Error[0] }))
                } else if (err.fault && err.fault.error && Array.isArray(err.fault.error)) {
                    const errorDetail = err.fault.error[0]
                    console.log('Error detail:', errorDetail)
                    if (errorDetail.code === '3200' && errorDetail.message.includes('AuthenticationFailed')) {
                        try {
                            console.log('token expired')
                            const tokenResponse = await refreshAccessTokenAPI(refreshtoken)
                            console.log('Token refreshed:', tokenResponse)

                            // Update qbo instance with new access token
                            qbo = new QuickBooks(
                                clientId,
                                clientSecret,
                                tokenResponse.access_token,
                                false, // No token secret needed for OAuth2.
                                realmId,
                                true, // Use the sandbox environment
                                false, // Debug mode off
                                null, // Endpoint version
                                '2.0', // OAuth version
                                tokenResponse.refresh_token,
                            )

                            // Retry creating the customer with the updated qbo instance
                            const newCustomer = await createCustomer(qbo, customerData)
                            resolve({ success_status: true, message: 'Customer created successfully', customer: newCustomer })
                        } catch (refreshError) {
                            console.error('Error refreshing access token:', refreshError)
                            reject(new Error({ success_status: false, message: refreshError }))
                        }
                    } else {
                        console.log('other fault error')
                        console.log('Error detail:', errorDetail)
                        reject(new Error({ success_status: false, message: err.fault.error[0] })) // Other fault errors
                    }
                } else {
                    reject(new Error({ success_status: false }))
                }
            } else {
                resolve({ success_status: true, message: customer }) // Resolve with the customer data
            }
        })
    })
}r

3. Save the credit card of the customer

async function storeCard(customerId, cardData) {
    try {
        const cardUrl = `${SANDBOX_API_BASE}/quickbooks/v4/customers/${customerId}/cards`
        const accessToken = await refreshAccessTokenAPI(refreshToken)
        const requestID = uuidv4()
        console.log('Request ID:', requestID)
        const headers = {
            Authorization: `Bearer ${accessToken.access_token}`,
            'Content-Type': 'application/json',
            'request-id': requestID,
        }
        const response = await axios.post(cardUrl, cardData, { headers })
        console.log(response)
        return response.data
    } catch (error) {
        console.error('Error:', error)
        return error
    }
}
module.exports.saveCard = async (event) => {
    const cardData = JSON.parse(event.body)
    // ID of the  created customer in quickbooks
    const customerId = event.queryStringParameters.cust_id
    try {
        const cardResponse = await storeCard(customerId, cardData)
        console.log('Card stored successfully:', JSON.stringify(cardResponse))
        if (cardResponse.id) {
            return {
                statusCode: 200,
                body: JSON.stringify({ success: true, card: cardResponse }),
            }
        }
        return {
            statusCode: 200,
            body: JSON.stringify({ success: true, card: cardResponse }),
        }
    } catch (error) {
        console.error('Error:', error)
        return {
            statusCode: 500,
            body: JSON.stringify({ success: false }),
        }
    }
}

Conclusion

This integration enables seamless QuickBooks payment processing, customer management, and order automation using AWS Lambda. For enhancements, consider adding webhooks to get real-time payment status updates.


References

QuickBooks Developer Portal

QuickBooks API DOCS

QuickBooks Playground to obtain tokens

Docs for node-quickbooks package(Supports accounting)

Docs for quickbooks package(supports payments)

3 Likes