-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Abandoned? #142
Comments
They updated PHP out of all things and not node.js... the #1 developer rated backend framework.. |
Seriously |
After 18 hours of understanding their very weirdly designed documentation, I just ended up creating a custom class to just do what I want it to do. Sad considering I'm also using azure api to process invoices and read text from them and their documentation is 100x better and their packages are actually kept up to date |
@m1daz Any chance I could snag that from you? |
import { GetPrisma } from "@/lib/database";
export class Quickbooks {
private redirectUrl: string;
private scope: string;
private clientId: string;
private clientSecret: string | undefined;
private readonly bearerUrl =
"https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
constructor(
redirectUrl: string,
scope: string,
clientId: string,
clientSectet?: string
) {
this.redirectUrl = redirectUrl;
this.scope = scope;
this.clientId = clientId;
this.clientSecret = clientSectet;
}
get OAUTH_URL(): string {
return `https://appcenter.intuit.com/app/connect/oauth2?client_id=${this.clientId}&scope=${this.scope}&redirect_uri=${this.redirectUrl}&response_type=code&state=PlaygroundAuth`;
}
private chunkBase64(b64: string): string {
// Split b64 string with /r/n to avoid exceeding max line size (1000)
// So for each 900 char for example, /r/n
// dont add /r/n to last item
const chunkSize = 72;
const chunks = [];
for (let i = 0; i < b64.length; i += chunkSize) {
chunks.push(b64.slice(i, i + chunkSize));
}
return chunks.join("\r\n");
}
async createInvoice(
realmId: string,
token: string,
customerId: string,
loadInformation: {
amount: number;
number: number;
}
): Promise<boolean> {
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/invoice#create-an-invoice
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities//attachable#upload-attachments
const resp = await fetch(
`https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/invoice`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
Line: [
{
DetailType: "SalesItemLineDetail",
Amount: loadInformation.amount,
SalesItemLineDetail: {
ItemRef: {
name: "Services",
value: "1",
},
},
},
],
CustomerRef: {
value: customerId,
},
}),
}
);
const data = await resp.json();
if (data && data.Invoice) {
const invoiceId: string = data.Invoice.Id;
return await this.uploadAttachment(
realmId,
token,
invoiceId,
loadInformation.number
);
} else {
return false;
}
}
async uploadAttachment(
realmId: string,
token: string,
invoiceId: string,
loadId: number
): Promise<boolean> {
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities//attachable#upload-attachments
const clientHandle = await GetPrisma();
if (!clientHandle.success) {
return false;
}
const client = clientHandle.prismaHandle!;
const invoiceFile = await client.invoice.findFirst({
where: {
load: {
id: loadId,
},
},
});
if (!invoiceFile) {
return false;
}
let pdfData = Buffer.from(invoiceFile.data ?? "", "base64");
if (pdfData.toString().split(";base64,").length > 1) {
pdfData = Buffer.from(pdfData.toString().split(";base64,")[1], "base64");
}
const body = `--dEneMo239
Content-Disposition: form-data; name="file_metadata_01"; filename="attachment.json"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit
{
"AttachableRef": [
{"EntityRef": {
"type": "Invoice",
"value": "${invoiceId}"
}
}
],
"FileName": "invoice.pdf",
"ContentType": "application/pdf"
}
--dEneMo239
Content-Disposition: form-data; name="file_content_01"; filename="invoice.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: base64
${this.chunkBase64(pdfData.toString("base64"))}
--dEneMo239--`;
// calculate body size in mb
const bodySize = Buffer.byteLength(body, "utf8") / 1024 / 1024;
const resp = await fetch(
`https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/upload`,
{
method: "POST",
headers: {
"Content-Type": "multipart/form-data; boundary=dEneMo239",
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
body: body,
}
);
const data = await resp.json();
if (data && data.AttachableResponse) {
return true;
} else {
return false;
}
}
async createCustomer(
realmId: string,
token: string,
customerInformation: {
Name: string;
EmailAddress?: string;
Phone?: string;
Address: {
Street: string;
City: string;
Zip: string;
State: string;
};
}
): Promise<string | undefined> {
// https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/customer#create-a-customer
const resp = await fetch(
`https://sandbox-quickbooks.api.intuit.com/v3/company/${realmId}/customer`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
FullyQualifiedName: customerInformation.Name,
PrimaryEmailAddr: {
Address: customerInformation.EmailAddress ?? "",
},
DisplayName: customerInformation.Name,
PrimaryPhone: {
FreeFormNumber: customerInformation.Phone ?? "",
},
CompanyName: customerInformation.Name,
BillAddr: {
CountrySubDivisionCode: customerInformation.Address.State,
City: customerInformation.Address.City,
PostalCode: customerInformation.Address.Zip,
Line1: customerInformation.Address.Street,
Country: "USA",
},
}),
}
);
const data = await resp.json();
if (
data.responseHeader === undefined ||
data.responseHeader.status === 200
) {
return data.Customer.Id;
}
return undefined;
}
async getAccessToken(code: string): Promise<{
access_token: string;
expires_in: number;
refresh_token: string;
x_refresh_token_expires_in: number;
token_type: string;
}> {
// convert clientId & secret to base64 (nodejs)
const bearer = Buffer.from(
`${this.clientId}:${this.clientSecret}`
).toString("base64");
const resp = await fetch(this.bearerUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
Authorization: `Basic ${bearer}`,
},
body: `grant_type=authorization_code&code=${code}&redirect_uri=${this.redirectUrl}`,
});
const data: any = await resp.json();
return data;
}
async refreshAccessToken(refreshToken: string): Promise<{
access_token: string;
expires_in: number;
refresh_token: string;
x_refresh_token_expires_in: number;
token_type: string;
}> {
const bearer = Buffer.from(
`${this.clientId}:${this.clientSecret}`
).toString("base64");
const resp = await fetch(this.bearerUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
Authorization: `Basic ${bearer}`,
},
body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
});
const data: any = await resp.json();
return data;
}
} I made it work for me, you might need to modify it for you. |
@m1daz Cool ty. I'm still trying to get everything authenticated but a few of those methods will be helpful for sure. Cheers |
Hi @geoffcorey, @m1daz, @WillsWebsites - Robert with the Intuit team here. Thank you for your comments and contributions. You're absolutely right, we haven't done a great job of keeping this OAuth Client up to date. I'm happy to share, however, that this is changing. Over the next few months, we'll be conducting a full review (including actioning all open PRs + issues) alongside other Intuit SDKs/OAuth Clients. From there, expect to see regular updates across the board. |
Thank you so much. I am very interested in working with intuit for our software, but I need to have the confidence in there being support. |
@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:
Otherwise with intuit api's in general:
I can post these elsewhere if needed just let me know |
@m1daz You bet! Check out the docs if you haven't already. Lots of great info there.
@WillsWebsites Fantastic suggestions, thank you! |
@robert-mings any update on when security patches and types will be pushed out for this sdk? |
Would love to see some updates on security patches and/or typescript support. |
Thank you all for your patience, much appreciated! Ensuring you that this SDK is still active and being maintained. We have plans to release the security patches asap, before we get too busy with the upcoming holiday season. Stay tuned! |
Created a branch called 'hotfix-4.0.1' and ran npm audit for security fixes. Feel free to test the branch, or raise PR on it and provide any comment or suggestions you may have. Thanks |
I moved on and use @apigrate/quickbooks now. Years without security patches, being maintained by hacktoberfest and interns is not acceptable for a company dealing with finances. |
Robert, today I had to modify some code for the quickbooks API and I decided to install this package instead of using my class that I made by reading the conversation. I was really happy seeing all the other changes, however, there's still one really big downfall for me right now. There's no TypeScript support whatsoever. This was promised previously, is it still coming? |
No security package upgrades, no updates in several years.
The text was updated successfully, but these errors were encountered: