Invoice Creation Within a Workflow Custom Code Action

Hi All

Apologies in advance as I am not a coder, but, I’m trying to create a custom coded action in a workflow to create an invoice automatically from a deal.

I’ve managed to create the invoice record but it doesn’t seem to let me fill out any of the fields on the invoice, or, output the newly created Invoice ID so I can associate it back to the deal.

I’ve attached a screenshot of the code I’ve been using. Can someone let me know where I’m going wrong or if this is even possible?

Thanks

@JackCoopersmith

Your screenshot didn’t make it.
How are you creating the Invoice Record? What isn’t letting you fill out fields on the invoice?

Here is the code I’m using in a custom coded workflow action;

const hubspot = require(‘@hubspot/api-client’);

exports.main = async (event, callback) => {
const hubspotClient = new hubspot.Client({
accessToken: process.env.invoice_creation
});

const dealName = event.inputFields[‘dealName’];
const amount = event.inputFields[‘amount’];
const paymentDueDate = event.inputFields[‘paymentduedate’];

try {
const createInvoice = await hubspotClient.apiRequest({
method: ‘POST’,
path: ‘/crm/v3/objects/invoices’,
body: {
properties: {
hs_currency: ‘GBP’
}
}
});

const invoiceId = createInvoice.body?.id;
if (!invoiceId) throw new Error(‘Invoice ID not found in response.’);

await hubspotClient.apiRequest({
method: ‘PATCH’,
path: `/crm/v3/objects/invoices/${invoiceId}`,
body: {
properties: {
hs_invoice_name: `Invoice for ${dealName}`,
hs_invoice_amount: amount,
hs_invoice_status: ‘draft’,
hs_invoice_date: new Date().toISOString().split(‘T’)[0],
hs_due_date: paymentDueDate,
hs_payment_terms: ‘Due on receipt’
}
}
});

console.log(‘Invoice successfully created and updated. ID:’, invoiceId);

callback({
outputFields: {
invoiceId: invoiceId,
error: ‘’
}
});

} catch (error) {
console.error(‘Invoice creation error:’, error.message);
callback({
outputFields: {
invoiceId: ‘’,
error: error.message
}
});
}
};

Thanks

I’m not familiar with how the api client responds, I use axios instead of that sdk. I added some comments here, I’m assuming you aren’t getting the invoice id from your api call. you just need to console.log that stuff out and review what the actual response id is and that should get you there.

const hubspot = require('@hubspot/api-client')

exports.main = async (event, callback) => {
 const hubspotClient = new hubspot.Client({
 accessToken: process.env.invoice_creation
 })

 const dealName = event.inputFields['dealName']
 const amount = event.inputFields['amount']
 const paymentDueDate = event.inputFields['paymentduedate']

 try {
 const createInvoice = await hubspotClient.apiRequest({
 method: 'POST',
 path: '/crm/v3/objects/invoices',
 body: {
 properties: {
 hs_currency: 'GBP'
 }
 }
 })
 const json = await response.json()
 console.log(json)
 // determine what the invoice id property is here
 // that may be json.data.id or something else, I have no clue, but if you look at the logged response you 
 // should be able to figure it out
 const invoiceId = createInvoice.body?.id
 if (!invoiceId) throw new Error('Invoice ID not found in response.')

 await hubspotClient.apiRequest({
 method: 'PATCH',
 path: `/crm/v3/objects/invoices/${invoiceId}`,
 body: {
 properties: {
 hs_invoice_name: `Invoice for ${dealName}`,
 hs_invoice_amount: amount,
 hs_invoice_status: 'draft',
 hs_invoice_date: new Date().toISOString().split('T')[0],
 hs_due_date: paymentDueDate,
 hs_payment_terms: 'Due on receipt'
 }
 }
 })

 console.log('Invoice successfully created and updated. ID:', invoiceId)

 callback({
 outputFields: {
 invoiceId: invoiceId,
 error: ''
 }
 })

 } catch (error) {
 console.error('Invoice creation error:', error.message)
 callback({
 outputFields: {
 invoiceId: '',
 error: error.message
 }
 })
 }
}

Hey @OBradley5,

Creating an invoice within a HubSpot workflow using custom code is achievable, but there are specific considerations to ensure all desired fields are populated and the invoice is correctly associated with the originating deal.

Your current approach involves two separate API calls: one to create the invoice and another to update its properties. While this method works, it’s more efficient to populate all necessary fields during the initial creation.

Here’s how you can modify your code to include all properties in the initial POST request,

const hubspot = require('@hubspot/api-client');

exports.main = async (event, callback) => {
 const hubspotClient = new hubspot.Client({
 accessToken: process.env.invoice_creation
 });

 const dealName = event.inputFields['dealName'];
 const amount = event.inputFields['amount'];
 const paymentDueDate = event.inputFields['paymentduedate'];

 try {
 const createInvoice = await hubspotClient.apiRequest({
 method: 'POST',
 path: '/crm/v3/objects/invoices',
 body: {
 properties: {
 hs_invoice_name: `Invoice for ${dealName}`,
 hs_invoice_amount: amount,
 hs_invoice_status: 'draft',
 hs_invoice_date: new Date().toISOString().split('T')[0],
 hs_due_date: paymentDueDate,
 hs_payment_terms: 'Due on receipt',
 hs_currency: 'GBP'
 }
 }
 });

 const invoiceId = createInvoice.body?.id;
 if (!invoiceId) throw new Error('Invoice ID not found in response.');

 console.log('Invoice successfully created. ID:', invoiceId);

 callback({
 outputFields: {
 invoiceId: invoiceId,
 error: ''
 }
 });

 } catch (error) {
 console.error('Invoice creation error:', error.message);
 callback({
 outputFields: {
 invoiceId: '',
 error: error.message
 }
 });
 }
};

To associate the newly created invoice with the originating deal, you’ll need to use the CRM Associations API.

Example :

const dealId = event.object.objectId;

await hubspotClient.crm.associations.v4.batchApi.create({
 inputs: [
 {
 from: { id: invoiceId },
 to: { id: dealId },
 types: [
 {
 associationCategory: 'HUBSPOT_DEFINED',
 associationTypeId: 3 // Replace with the correct association type ID
 }
 ]
 }
 ]
});

The associationTypeId should correspond to the correct association between invoices and deals. You can retrieve the appropriate ID using the CRM Association Types API.

Additional : Ensure that your private app has the necessary scopes, such as crm.objects.invoices.write and crm.objects.deals.read, to perform these operations.

Regards,

Kosala