oauth-jsclient icon indicating copy to clipboard operation
oauth-jsclient copied to clipboard

Abandoned?

Open geoffcorey opened this issue 1 year ago • 16 comments

No security package upgrades, no updates in several years.

geoffcorey avatar Jul 18 '23 15:07 geoffcorey

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

m1daz avatar Aug 16 '23 02:08 m1daz

Seriously

WillsWebsites avatar Aug 16 '23 20:08 WillsWebsites

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 avatar Aug 16 '23 20:08 m1daz

@m1daz Any chance I could snag that from you?

WillsWebsites avatar Aug 16 '23 20:08 WillsWebsites

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 avatar Aug 16 '23 20:08 m1daz

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

WillsWebsites avatar Aug 16 '23 21:08 WillsWebsites

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!

robert-mings avatar Aug 18 '23 22:08 robert-mings

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 avatar Aug 21 '23 20:08 m1daz

@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 avatar Aug 24 '23 23:08 WillsWebsites

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-mings avatar Aug 30 '23 02:08 robert-mings

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

EthanDavis avatar Sep 28 '23 20:09 EthanDavis

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

abtonc avatar Oct 02 '23 09:10 abtonc

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 avatar Oct 05 '23 18:10 rajeshgupta723

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

rajeshgupta723 avatar Oct 11 '23 06:10 rajeshgupta723

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.

geoffcorey avatar Nov 28 '23 15:11 geoffcorey

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?

m1daz avatar May 28 '24 15:05 m1daz