Skip to content
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

Open
geoffcorey opened this issue Jul 18, 2023 · 16 comments
Open

Abandoned? #142

geoffcorey opened this issue Jul 18, 2023 · 16 comments

Comments

@geoffcorey
Copy link

No security package upgrades, no updates in several years.

@m1daz
Copy link

m1daz commented Aug 16, 2023

They updated PHP out of all things and not node.js... the #1 developer rated backend framework..

@WillsWebsites
Copy link

Seriously

@m1daz
Copy link

m1daz commented Aug 16, 2023

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

@WillsWebsites
Copy link

@m1daz Any chance I could snag that from you?

@m1daz
Copy link

m1daz commented Aug 16, 2023

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.

@WillsWebsites
Copy link

@m1daz Cool ty. I'm still trying to get everything authenticated but a few of those methods will be helpful for sure. Cheers

@robert-mings
Copy link
Collaborator

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.
Outside of this thread, if you have any additional suggestions, please send them our way!

@m1daz
Copy link

m1daz commented Aug 21, 2023

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.

Outside of this thread, if you have any additional suggestions, please send them our way!

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.

@WillsWebsites
Copy link

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

  • Typescript with all defined types and responses
  • Updating packages that have security flaws

Otherwise with intuit api's in general:

  • Being able to get the payment invoice link as you're able to in the software itself (maybe it can be included in the invoice query response)
  • Maybe the ability to not have to set up a full Oauth2 system to work with your companies own data if possible?
  • An official API SDK instead of relying on the community one.

I can post these elsewhere if needed just let me know

@robert-mings
Copy link
Collaborator

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.
Outside of this thread, if you have any additional suggestions, please send them our way!

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.

@m1daz You bet! Check out the docs if you haven't already. Lots of great info there.

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

* Typescript with all defined types and responses

* Updating packages that have security flaws

Otherwise with intuit api's in general:

* Being able to get the payment invoice link as you're able to in the software itself (maybe it can be included in the invoice query response)

* Maybe the ability to not have to set up a full Oauth2 system to work with your companies own data if possible?

* An official API SDK instead of relying on the community one.

I can post these elsewhere if needed just let me know

@WillsWebsites Fantastic suggestions, thank you!

@EthanDavis
Copy link

@robert-mings any update on when security patches and types will be pushed out for this sdk?

@abtonc
Copy link

abtonc commented Oct 2, 2023

Would love to see some updates on security patches and/or typescript support.

@rajeshgupta723
Copy link
Collaborator

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!

@rajeshgupta723
Copy link
Collaborator

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

@geoffcorey
Copy link
Author

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.

@m1daz
Copy link

m1daz commented May 28, 2024

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.
Outside of this thread, if you have any additional suggestions, please send them our way!

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.

@m1daz You bet! Check out the docs if you haven't already. Lots of great info there.

@robert-mings That'd be great to see. The main things with this specific package that would be useful to start are:

* Typescript with all defined types and responses

* Updating packages that have security flaws

Otherwise with intuit api's in general:

* Being able to get the payment invoice link as you're able to in the software itself (maybe it can be included in the invoice query response)

* Maybe the ability to not have to set up a full Oauth2 system to work with your companies own data if possible?

* An official API SDK instead of relying on the community one.

I can post these elsewhere if needed just let me know

@WillsWebsites Fantastic suggestions, thank you!

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants