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_IDCLIENT_SECRETREDIRECT_URIREALM_IDACCESS_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)
- Get Authorization Code
- Redirect the user to QuickBooks authorization URL:
https://appcenter.intuit.com/connect/oauth2
- Use scope:
com.intuit.quickbooks.paymentandcom.intuit.quickbooks.accounting
- Exchange Code for Access Token
- Use
client_id,client_secret, andauthorization_codeto obtain anaccess_tokenandrefresh_token.
- Refresh Token Automatically
- Use the
refresh_tokento get a newaccess_tokenbefore 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 Playground to obtain tokens
