unit qbAPI;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, AdvUtil, Data.DB, Vcl.Grids, AdvObj,
  BaseGrid, AdvGrid, DBAdvGrid, MemDS, DBAccess, Uni, Vcl.StdCtrls, Vcl.Mask,
  vcl.wwdbedit, vcl.wwdotdot, vcl.wwdbcomb, REST.Client, REST.Types, System.JSON,
  System.Generics.Collections, AdvEdit, vcl.wwdblook, vcl.wwdbdatetimepicker,
  System.Hash, Api.Database, Vcl.ExtCtrls, WEBLib.Forms, WEBLib.Controls, WEBLib.StdCtrls,
  WEBLib.ExtCtrls, WEBLib.REST, WEBLib.WebTools,System.Net.HttpClient,
  System.Net.URLClient, System.Net.HttpClientComponent, System.netencoding,
  IdHTTP, IdSSLOpenSSL, IdSSLOpenSSLHeaders, System.DateUtils, System.IniFiles;


type
  TfQB = class(TForm)
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    uq: TUniQuery;
    uqORDER_ID: TIntegerField;
    uqCOMPANY_ID: TIntegerField;
    uqUSER_ID: TIntegerField;
    uqORDER_DATE: TDateTimeField;
    uqSTART_DATE: TDateField;
    uqEND_DATE: TDateField;
    uqORDER_STATUS: TStringField;
    uqSCHED_JSON: TStringField;
    uqstaff_fields_order_date: TDateField;
    uqstaff_fields_proof_date: TDateField;
    uqstaff_fields_ship_date: TDateField;
    uqstaff_fields_ship_via: TStringField;
    uqstaff_fields_price: TStringField;
    uqstaff_fields_invoice_to: TStringField;
    uqstaff_fields_invoice_attention: TStringField;
    uqstaff_fields_ship_to: TStringField;
    uqstaff_fields_ship_attention: TStringField;
    uqstaff_fields_po_number: TStringField;
    uqstaff_fields_job_name: TStringField;
    uqstaff_fields_art_due: TDateField;
    uqstaff_fields_plate_due: TDateField;
    uqplates_job_number: TStringField;
    uqsupplied_by_customer_b_w_or_co: TStringField;
    uqsupplied_by_customer_plates: TStringField;
    uqsupplied_by_customer_sample: TStringField;
    uqsupplied_by_customer_dimension: TStringField;
    uqsupplied_by_customer_other: TStringField;
    uqsupplied_by_customer_disk: TStringField;
    uqsupplied_by_customer_e_mail: TStringField;
    uqsupplied_by_customer_ftp: TStringField;
    uqplates_plate_material: TStringField;
    uqplates_thickness: TStringField;
    uqsupplied_by_customer_total_inc: TStringField;
    uqsupplied_by_customer_sheets_us: TStringField;
    uqsupplied_by_customer_initials: TStringField;
    uqproofing_pdf: TStringField;
    uqproofing_pdf_to: TStringField;
    uqproofing_pdf_date_1: TDateField;
    uqproofing_pdf_date_2: TDateField;
    uqproofing_pdf_date_3: TDateField;
    uqproofing_full_size_ink_jet_for: TStringField;
    uqproofing_ink_jet_to: TStringField;
    uqproofing_ink_jet_to_2: TStringField;
    uqproofing_ink_jet_date_1: TDateField;
    uqproofing_ink_jet_date_2: TDateField;
    uqproofing_ink_jet_date_3: TDateField;
    uqproofing_color_contract: TStringField;
    uqproofing_color_contrac_to: TStringField;
    uqproofing_color_contrac_date_1: TDateField;
    uqproofing_color_contrac_date_2: TDateField;
    uqproofing_digital_color_key: TStringField;
    uqproofing_digital_color_to: TStringField;
    uqproofing_digital_color_date_1: TDateField;
    uqquantity_and_colors_press_name: TStringField;
    uqquantity_and_colors_anilox_info: TStringField;
    uqplate_marks_microdots: TStringField;
    uqplate_marks_microdots_comments: TStringField;
    uqplate_marks_crosshairs: TStringField;
    uqplate_marks_crosshairs_comments: TStringField;
    uqplate_marks_color_bars: TStringField;
    uqplate_marks_color_bars_comments: TStringField;
    uqplate_marks_other: TStringField;
    uqplate_marks_other_comments: TStringField;
    uqprint_orientation_print_orient: TStringField;
    uqlayout_around: TStringField;
    uqlayout_accross: TStringField;
    uqlayout_surface_print: TStringField;
    uqlayout_reverse_print: TStringField;
    uqlayout_cylinder_repeat: TStringField;
    uqlayout_cutoff_dimension: TStringField;
    uqlayout_pitch: TStringField;
    uqlayout_teeth: TStringField;
    uqlayout_bleed: TStringField;
    uqlayout_cutback: TStringField;
    uqlayout_minimum_trap_dim: TStringField;
    uqlayout_maximum_trap_dim: TStringField;
    uqupc_size: TStringField;
    uqupc_bar_width_reduction: TStringField;
    uqquantity_and_colors_qty_colors: TStringField;
    uqgeneral_comments: TStringField;
    uqstaff_fields_quickbooks_item: TStringField;
    uqstaff_fields_quantity: TStringField;
    uqupc_distortion_percent: TStringField;
    uqupc_distortion_amount: TStringField;
    uqstaff_fields_art_location: TStringField;
    Memo1: TMemo;
    Button6: TButton;
    Button7: TButton;
    Button1: TButton;
    Panel1: TPanel;
    asgData2: TAdvStringGrid;
    Panel2: TPanel;
    Splitter1: TSplitter;
    asgData: TAdvStringGrid;
    Splitter2: TSplitter;
    Button8: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure asgData2ClickCell(Sender: TObject; ARow, ACol: Integer);
    procedure asgDataClickCell(Sender: TObject; ARow, ACol: Integer);
    procedure Panel1Resize(Sender: TObject);
    procedure Panel2Resize(Sender: TObject);
    procedure Button8Click(Sender: TObject);
  private
    { Private declarations }
    strict private
    ordersDB: TApiDatabase;
     httpReqTokenRefresh: TWebHttpRequest;

     var
      AccessToken,RefreshToken,CompanyID,Client,Secret: string;
      LastRefresh: TDateTime;
  public
    { Public declarations }
    procedure findMatches(CustomerList: TJSONArray; JSONfield, DBField: string);
    procedure getCompanyInfo();
    procedure LoadJsonArray(jaData: TJSONArray);
    procedure LoadJsonArray2(jaData: TJSONArray);
    function RefreshAccessToken(): string;
    procedure ConfigureSSL(IOHandler: TIdSSLIOHandlerSocketOpenSSL);
    procedure SaveTokens(AccessToken, RefreshToken: string);
    procedure getCustomers();
  end;

var
  fQB: TfQB;

implementation

uses
  Common.Logging, uLibrary, Lookup.Service, Lookup.ServiceImpl;

{$R *.dfm}

procedure TfQB.asgData2ClickCell(Sender: TObject; ARow, ACol: Integer);
var
  point, origin: TPoint;
begin
  origin := TPoint.Create(1,1);
  point := asgData.find(origin, asgData2.Cells[2, ARow], []);
  asgData.Row := point.Y;
  asgData.Col := point.X;
end;

procedure TfQB.asgDataClickCell(Sender: TObject; ARow, ACol: Integer);
var
  point, origin: TPoint;
begin
  origin := TPoint.Create(1,1);
  point := asgData2.find(origin, asgData.Cells[2, ARow], []);
  asgData2.Row := point.Y;
  asgData2.Col := point.X;
end;

procedure TfQB.Button1Click(Sender: TObject);
var
  SQL: string;
  count: integer;
  ModifiedList: TJSONArray;
  Customer, ParsedCustomer: TJSONObject;
  I: integer;
begin
  SQL := 'SELECT * FROM customers c';
  doQuery(ordersDB.UniQuery1, SQL);
  ModifiedList := TJSONArray.Create;

  while not ordersDB.UniQuery1.Eof do
  begin
    ParsedCustomer := TJSONObject.Create;

      //sql := 'select CUSTOMER_ID from customers where QB_LIST_ID = ' +  Customer.GetValue<string>('Id');
      //doQuery(ordersDB.UniQuery1, SQL);

      try
        //ParsedCustomer.AddPair('In KGOrders', not(ordersDB.UniQuery1.IsEmpty));

      ParsedCustomer.AddPair('Id', ordersDB.UniQuery1.FieldByName('CUSTOMER_ID').AsString);
      ParsedCustomer.AddPair('CompanyName', ordersDB.UniQuery1.FieldByName('NAME').AsString);
      ParsedCustomer.AddPair('BillAddrCity', ordersDB.UniQuery1.FieldByName('BILL_CITY').AsString);
      ParsedCustomer.AddPair('BillAddrLine1',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS').AsString);
      ParsedCustomer.AddPair('BillAddrState',  ordersDB.UniQuery1.FieldByName('BILL_STATE').AsString);
      ParsedCustomer.AddPair('BillAddrZip',  ordersDB.UniQuery1.FieldByName('BILL_ZIP').AsString);
      ParsedCustomer.AddPair('BillAddr',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS_BLOCK').AsString);


      ModifiedList.AddElement(ParsedCustomer);
      ordersDB.UniQuery1.Next;
      except
        ParsedCustomer.Free;
        raise;
      end;
  end;

  LoadJSONArray2(ModifiedList);

end;

procedure TfQB.SaveTokens(AccessToken, RefreshToken: string);
var
  f: TStringList;
  iniStr, line: string;
  iniFile: TIniFile;
begin
  iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
  try
    iniFile.WriteString('Quickbooks', 'RefreshToken', RefreshToken);
    LastRefresh := Now;
    Logger.Log(1, 'Tokens Successfully Saved');

  finally
    IniFile.Free;
  end;
  f := TStringList.Create;


    // Save to file (overwrites existing file)
  f.SaveToFile('QB.txt');
  f.Free;
end;

procedure TfQB.Button2Click(Sender: TObject);
begin
  GetCustomers();
end;

procedure TfQB.Button3Click(Sender: TObject);
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  Customer: TJSONValue;
  jsObj: TJSONObject;
  CustomerList: TJSONArray;
  pair: TJSONPair;
begin
  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;

  restRequest.Method := rmGET;
  //GET /v3/company/<realmId>/customer/<customerId>
  res := '/v3/company/' + companyid + '/customer/58';
  restRequest.Resource := res;

  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  //memo1.Lines.Add(restresponse.Content);

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);
  Memo1.Lines.Add( jsObj.Format(2) );

 // CustomerList := TJSONArray( TJSONObject( jsObj.GetValue('QueryResponse') ).GetValue('Customer')) ;

 // LoadJSONArray( CustomerList );

  restClient.Free;
  restRequest.Free;
  restResponse.Free;

end;

procedure TfQB.Button4Click(Sender: TObject);
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  Customer: TJSONValue;
  jsObj: TJSONObject;
  CustomerList: TJSONArray;
  pair: TJSONPair;
  estimateJSON: TJSONObject;
  SQL: string;
  estimate: TEstimate;
begin

  SQL := 'select * from corrugated_plate_orders cpo join customers c on cpo.COMPANY_ID = c.CUSTOMER_ID where ORDER_ID = 18690';
  doQuery(ordersDB.UniQuery1, SQL);

  estimate.TxnDate := DateTimeToStr(Now);
  estimate.PrivateNote := ordersDB.UniQuery1.FieldByName('general_special_instructions').AsString;
  //estimate.CustomerRef := order


  ///v3/company/<realmID>/estimate
  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;

  restRequest.Method := rmPOST;
  res := '/v3/company/' + companyID + '/estimate';
  restRequest.Resource := res;

  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  //memo1.Lines.Add(restresponse.Content);

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);
  Memo1.Lines.Add( jsObj.Format(2) );
  //CustomerList := TJSONArray(restResponse.JSONValue);

 // CustomerList := TJSONArray( TJSONObject( jsObj.GetValue('QueryResponse') ).GetValue('Customer')) ;

 // LoadJSONArray( CustomerList );

  restClient.Free;
  restRequest.Free;
  restResponse.Free;
end;

procedure TfQB.Button5Click(Sender: TObject);
var
  SQL: string;
  count: integer;
  ModifiedList: TJSONArray;
  Customer, ParsedCustomer: TJSONObject;
  I: integer;
begin
  SQL := 'SELECT * FROM customers c ' +
          'WHERE EXISTS (SELECT 1 FROM corrugated_plate_orders cpo WHERE cpo.COMPANY_ID = c.CUSTOMER_ID) '+
          'OR EXISTS (SELECT 1 FROM web_plate_orders wpo WHERE wpo.COMPANY_ID = c.CUSTOMER_ID) ORDER BY c.SHORT_NAME';
  doQuery(ordersDB.UniQuery1, SQL);
  ModifiedList := TJSONArray.Create;

  while not ordersDB.UniQuery1.Eof do
  begin
    ParsedCustomer := TJSONObject.Create;

      //sql := 'select CUSTOMER_ID from customers where QB_LIST_ID = ' +  Customer.GetValue<string>('Id');
      //doQuery(ordersDB.UniQuery1, SQL);

      try
        //ParsedCustomer.AddPair('In KGOrders', not(ordersDB.UniQuery1.IsEmpty));

      ParsedCustomer.AddPair('Id', ordersDB.UniQuery1.FieldByName('CUSTOMER_ID').AsString);
      ParsedCustomer.AddPair('CompanyName', ordersDB.UniQuery1.FieldByName('NAME').AsString);
      ParsedCustomer.AddPair('BillAddrCity', ordersDB.UniQuery1.FieldByName('BILL_CITY').AsString);
      ParsedCustomer.AddPair('BillAddrLine1',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS').AsString);
      ParsedCustomer.AddPair('BillAddrState',  ordersDB.UniQuery1.FieldByName('BILL_STATE').AsString);
      ParsedCustomer.AddPair('BillAddrZip',  ordersDB.UniQuery1.FieldByName('BILL_ZIP').AsString);
      ParsedCustomer.AddPair('BillAddr',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS_BLOCK').AsString);


      ModifiedList.AddElement(ParsedCustomer);
      ordersDB.UniQuery1.Next;
      except
        ParsedCustomer.Free;
        raise;
      end;
  end;

  LoadJSONArray2(ModifiedList);

end;

procedure TfQB.Button6Click(Sender: TObject);
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  jsObj: TJSONObject;
  CustomerList: TJSONArray;
  pair: TJSONPair;
  ModifiedList: TJSONArray;
  ParsedCustomer, Customer: TJSONObject;
  I: integer;
  BillAddr: TJSONObject;
  ShipAddr: TJSONObject;
  SQL: string;
begin
  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;

  restRequest.Method := rmGET;
  //res := '/v3/company/' + companyID + '/preferences';
  res := '/v3/company/' + companyID + '/query?query=select * from Customer&minorversion=75';  // '/customer/58';
  restRequest.Resource := res;
  ///query?query=select * from Customer where CompanyName = ' + quotedStr('TYOGA CONTAINER') + '&minorversion=75';


  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  //memo1.Lines.Add(restresponse.Content);

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);
  //CustomerList := TJSONArray(restResponse.JSONValue);

  CustomerList := TJSONArray( TJSONObject( jsObj.GetValue('QueryResponse') ).GetValue('Customer')) ;

  //LoadJSONArray( CustomerList );
  findMatches(CustomerList, 'DisplayName', 'Name');
  //findMatches(CustomerList, 'BillAddrLine1', 'BILL_ADDRESS');
  //findMatches(CustomerList, 'BillAddrCity', 'BILL_CITY');
  //findMatches(CustomerList, 'BillAddrState', 'BILL_STATE');
  //findMatches(CustomerList, 'BillAddrZip', 'BILL_ZIP');


end;

procedure TfQB.Button7Click(Sender: TObject);
var
  SQL: string;
  count: integer;
  ModifiedList: TJSONArray;
  Customer, ParsedCustomer: TJSONObject;
  I: integer;
begin
  SQL := 'SELECT * FROM customers c ' +
          'WHERE NOT EXISTS (SELECT 1 FROM corrugated_plate_orders cpo WHERE cpo.COMPANY_ID = c.CUSTOMER_ID) '+
          'AND NOT EXISTS (SELECT 1 FROM web_plate_orders wpo WHERE wpo.COMPANY_ID = c.CUSTOMER_ID) ORDER BY c.SHORT_NAME';
  doQuery(ordersDB.UniQuery1, SQL);
  ModifiedList := TJSONArray.Create;

  while not ordersDB.UniQuery1.Eof do
  begin
    ParsedCustomer := TJSONObject.Create;

      //sql := 'select CUSTOMER_ID from customers where QB_LIST_ID = ' +  Customer.GetValue<string>('Id');
      //doQuery(ordersDB.UniQuery1, SQL);

      try
        //ParsedCustomer.AddPair('In KGOrders', not(ordersDB.UniQuery1.IsEmpty));

      ParsedCustomer.AddPair('Id', ordersDB.UniQuery1.FieldByName('CUSTOMER_ID').AsString);
      ParsedCustomer.AddPair('CompanyName', ordersDB.UniQuery1.FieldByName('NAME').AsString);
      ParsedCustomer.AddPair('BillAddrCity', ordersDB.UniQuery1.FieldByName('BILL_CITY').AsString);
      ParsedCustomer.AddPair('BillAddrLine1',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS').AsString);
      ParsedCustomer.AddPair('BillAddrState',  ordersDB.UniQuery1.FieldByName('BILL_STATE').AsString);
      ParsedCustomer.AddPair('BillAddrZip',  ordersDB.UniQuery1.FieldByName('BILL_ZIP').AsString);
      ParsedCustomer.AddPair('BillAddr',  ordersDB.UniQuery1.FieldByName('BILL_ADDRESS_BLOCK').AsString);


      ModifiedList.AddElement(ParsedCustomer);
      ordersDB.UniQuery1.Next;
      except
        ParsedCustomer.Free;
        raise;
      end;
  end;

  LoadJSONArray2(ModifiedList);

end;

procedure TfQB.Button8Click(Sender: TObject);
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  jsObj: TJSONObject;
  CustomerList: TJSONArray;
  pair: TJSONPair;
  ModifiedList: TJSONArray;
  ParsedCustomer, Customer: TJSONObject;
  I: integer;
  BillAddr: TJSONObject;
  ShipAddr: TJSONObject;
begin

  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;

  restRequest.Method := rmGET;
  //res := '/v3/company/' + companyID + '/preferences';
  res := '/v3/company/' + companyID + '/query?query=select * from Item&minorversion=75';  // '/customer/58';
  restRequest.Resource := res;


  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  //memo1.Lines.Add(restresponse.Content);

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);
  //CustomerList := TJSONArray(restResponse.JSONValue);
  Memo1.Lines.Add( jsObj.Format(2) );

  CustomerList := TJSONArray( TJSONObject( jsObj.GetValue('QueryResponse') ).GetValue('Customer')) ;

  // Load customer info

  restClient.Free;
  restRequest.Free;
  restResponse.Free;


end;

procedure TfQB.getCustomers();
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  jsObj: TJSONObject;
  CustomerList: TJSONArray;
  pair: TJSONPair;
  ModifiedList: TJSONArray;
  ParsedCustomer, Customer: TJSONObject;
  I: integer;
  BillAddr: TJSONObject;
  ShipAddr: TJSONObject;
begin

  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;

  restRequest.Method := rmGET;
  //res := '/v3/company/' + companyID + '/preferences';
  res := '/v3/company/' + companyID + '/query?query=select * from Customer&minorversion=75';  // '/customer/58';
  restRequest.Resource := res;
  ///query?query=select * from Customer where CompanyName = ' + quotedStr('TYOGA CONTAINER') + '&minorversion=75';


  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  //memo1.Lines.Add(restresponse.Content);

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);
  //CustomerList := TJSONArray(restResponse.JSONValue);
  Memo1.Lines.Add( jsObj.Format(2) );

  CustomerList := TJSONArray( TJSONObject( jsObj.GetValue('QueryResponse') ).GetValue('Customer')) ;

  // Load customer info
  ModifiedList := TJSONArray.Create;
  for I := 0 to CustomerList.Count - 1 do
    begin
      Customer := CustomerList.Items[I] as TJSONObject;
      Memo1.Lines.Add( Customer.Format(2) );
      ParsedCustomer := TJSONObject.Create;

      //sql := 'select CUSTOMER_ID from customers where QB_LIST_ID = ' +  Customer.GetValue<string>('Id');
      //doQuery(ordersDB.UniQuery1, SQL);

      try
        //ParsedCustomer.AddPair('In KGOrders', not(ordersDB.UniQuery1.IsEmpty));

        ParsedCustomer.AddPair('Id', Customer.GetValue<string>('Id'));
        ParsedCustomer.AddPair('CompanyName', Customer.GetValue<string>('DisplayName'));

        // Handle Bill Address
        if Customer.GetValue('BillAddr') is TJSONObject then
        begin
          BillAddr := Customer.GetValue('BillAddr') as TJSONObject;
          ParsedCustomer.AddPair('BillAddrLine1', TJSONString.Create(BillAddr.GetValue<string>('Line1', '')));
          ParsedCustomer.AddPair('BillAddrCity', TJSONString.Create(BillAddr.GetValue<string>('City', '')));
          ParsedCustomer.AddPair('BillAddrState', TJSONString.Create(BillAddr.GetValue<string>('CountrySubDivisionCode', '')));
          ParsedCustomer.AddPair('BillAddrZip', TJSONString.Create(BillAddr.GetValue<string>('PostalCode', '')));
          ParsedCustomer.AddPair('BillAddr',
            TJSONString.Create(
              Customer.GetValue<string>('DisplayName') + sLineBreak +
              BillAddr.GetValue('Line1', '') + ',' + sLineBreak +
              BillAddr.GetValue('City', '') + ', ' +
              BillAddr.GetValue('CountrySubDivisionCode', '') + ' ' +
              BillAddr.GetValue('PostalCode', '')
            )
          );
        end;

        // Handle Ship Address
        if Customer.GetValue('ShipAddr') is TJSONObject then
        begin
          BillAddr := Customer.GetValue('ShipAddr') as TJSONObject;
          ParsedCustomer.AddPair('ShipAddrLine1', TJSONString.Create(BillAddr.GetValue<string>('Line1', '')));
          ParsedCustomer.AddPair('ShipAddrCity', TJSONString.Create(BillAddr.GetValue<string>('City', '')));
          ParsedCustomer.AddPair('ShipAddrState', TJSONString.Create(BillAddr.GetValue<string>('CountrySubDivisionCode', '')));
          ParsedCustomer.AddPair('ShipAddrZip', TJSONString.Create(BillAddr.GetValue<string>('PostalCode', '')));
          ParsedCustomer.AddPair('ShipAddr',
            TJSONString.Create(
              Customer.GetValue<string>('DisplayName') + sLineBreak +
              BillAddr.GetValue('Line1', '') + ',' + sLineBreak +
              BillAddr.GetValue('City', '') + ', ' +
              BillAddr.GetValue('CountrySubDivisionCode', '') + ' ' +
              BillAddr.GetValue('PostalCode', '')
            )
          );
        end;

      ModifiedList.AddElement(ParsedCustomer);
      except
        ParsedCustomer.Free;
        raise;
      end;
    end;

  LoadJSONArray(ModifiedList);

  restClient.Free;
  restRequest.Free;
  restResponse.Free;


end;

procedure TfQB.ConfigureSSL(IOHandler: TIdSSLIOHandlerSocketOpenSSL);
begin
  // For Indy 10.6.2+ (Delphi 10.2 Tokyo+)
  IOHandler.SSLOptions.Method := sslvTLSv1_2;

  // Set SSL versions - maximum compatibility
  IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];

  // For very old Indy versions (fallback)
  if not (sslvTLSv1_2 in IOHandler.SSLOptions.SSLVersions) then
  begin
    IOHandler.SSLOptions.Method := sslvSSLv23;
    IOHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
  end;

  IOHandler.SSLOptions.Mode := sslmClient;
end;

procedure TfQB.FormCreate(Sender: TObject);
var
  iniFile: TIniFile;
begin
  inherited;
  ordersDB := TApiDatabase.Create(nil);
  iniFile := TIniFile.Create( ExtractFilePath(Application.ExeName) + 'kgOrdersServer.ini' );
  Client := iniFile.ReadString('Quickbooks', 'ClientID', '');
  Secret := iniFile.ReadString('Quickbooks', 'ClientSecret', '');
  CompanyID := iniFile.ReadString('Quickbooks', 'CompanyID', '');
  RefreshToken := iniFile.ReadString('Quickbooks', 'RefreshToken', '');
end;

procedure TfQB.FormDestroy(Sender: TObject);
begin
  ordersDB.Free;
  inherited;
end;

function TfQB.RefreshAccessToken: string;
// Refresh Token changes so make sure to save refresh token.
var
  IdHTTP: TIdHTTP;
  SSLIO: TIdSSLIOHandlerSocketOpenSSL;
  RequestStream: TStringStream;
  EncodedAuth, EncodedAuth2, PostData, response: string;
  f: TStringList;
  fi: string;
  JSObj: TJSONObject;
  iniFile: TIniFile;
  Encoder: TBase64Encoding;
begin
  // 1. Encode credentials (same as working Postman request)

  // TNetEncoding.Base64.Encode adds a new line every 72 chars, this stops that
  Encoder := TBase64Encoding.Create(0);

  if( (Client = '') or (Secret = '') ) then
  begin
    Logger.Log(1, 'Missing Client ID or Client Secret in INI File');
    Exit();
  end;

  EncodedAuth := Encoder.Encode(Client + ':' + Secret);
  Memo1.Lines.Add(EncodedAuth);
  if RefreshToken = '' then
  begin
    Logger.Log(3, 'Missing Refresh Token, Please Manually Get a New One and Store in INI File');
    Exit();
  end;

  // 2. Prepare POST data (EXACTLY as in Postman)
  PostData := 'grant_type=refresh_token&refresh_token=' + RefreshToken;

  // 3. Configure HTTP client
  IdHTTP := TIdHTTP.Create(nil);
  SSLIO := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  try
    // Force TLS 1.2
    SSLIO.SSLOptions.Method := sslvTLSv1_2;
    SSLIO.SSLOptions.SSLVersions := [sslvTLSv1_2];
    IdHTTP.IOHandler := SSLIO;

    // Set headers (EXACT match with Postman)
    IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
    IdHTTP.Request.Accept := 'application/json';
    IdHTTP.Request.CustomHeaders.AddValue('Authorization', 'Basic ' + EncodedAuth);

    // 4. Create and send request
    RequestStream := TStringStream.Create(PostData, TEncoding.UTF8);
    try
      // Execute POST
      try
        response := IdHTTP.Post('https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer', RequestStream);
        JSObj :=  TJSONObject.ParseJSONValue(response) as TJSONObject;
        RefreshToken := JSObj.GetValue('refresh_token').ToString.Trim(['"']);
        AccessToken := JSObj.GetValue('access_token').ToString.Trim(['"']);
        SaveTokens(AccessToken, RefreshToken);
        Result := AccessToken;
        Logger.Log(1, 'qbAPI - Tokens Successfully Saved');
        Memo1.Lines.Add('Tokens Successfully Saved');
      except
        on E: EIdHTTPProtocolException do
          Memo1.Lines.Add('Error: ' + E.Message + #13#10 + 'Response: ' + E.ErrorMessage);
      end;
    finally
      RequestStream.Free;
    end;
  finally
    SSLIO.Free;
    IdHTTP.Free;
  end;
end;

procedure TfQB.getCompanyInfo();
var
  restClient: TRESTClient;
  restRequest: TRESTRequest;
  restResponse: TRESTResponse;
  param: TRESTRequestParameter;
  res: string;
  jsValue: TJSONValue;
  jsObj, companyInfo: TJSONObject;
begin
  restClient := TRESTClient.Create(nil);
  restClient.BaseURL := 'https://sandbox-quickbooks.api.intuit.com';

  restRequest := TRESTRequest.Create(nil);
  restRequest.Client := restClient;

  restResponse := TRESTResponse.Create(nil);
  restRequest.Response := restResponse;


  if MinutesBetween(Now, LastRefresh) > 58  then
  begin
    RefreshAccessToken();
  end;
  restRequest.Method := rmGET;
  res := '/v3/company/' + companyID + '/companyinfo/' + companyID;
  restRequest.Resource := res;

  param := restRequest.Params.AddItem;
  param.Name := 'Authorization';
  param.Kind := pkHTTPHEADER;
  param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
  param.Value := 'Bearer ' + AccessToken;

  restRequest.Execute;

  memo1.Lines.Add(restresponse.Content) ;
  //jsValue := restResponse.JSONValue;
  //jsObj := TJSONObject.ParseJSONValue(restresponse.Content) as TJSONObject;
  //companyInfo := TJSONObject(jsObj.GetValue('CompanyInfo'));

  restClient.Free;
  restRequest.Free;
  restResponse.Free;

end;

procedure TfQB.LoadJsonArray(jaData: TJSONArray);
var
  jso: TJSONObject;
  i, j: integer;
  row: integer;
begin
  Memo1.Lines.Add( '---------------------------------------------------------------' );
  Memo1.Lines.Add( 'LoadJsonArray into asgData' );

  asgData.ClearAll;
  asgData.RowCount := 1;
  asgData.StartUpdate;

  jso := TJSONObject(jaData.Items[0]);
  asgData.ColCount := jso.Count;
  for i := 0 to jso.Count - 1 do
    asgData.Cells[i+1, 0] := jso.Pairs[i].JsonString.Value;

  for i := 0 to jaData.Count - 1 do
  begin
    jso := TJSONObject(jaData.Items[i]);
    asgData.RowCount := asgData.RowCount + 1;
    row := asgData.RowCount - 1;
    for j := 0 to jso.Count - 1 do
      asgData.Cells[j+1, row] := jso.Pairs[j].JsonValue.Value;
  end;
  asgData.EndUpdate;
  asgData.AutoSizeColumns(true);
end;

procedure TfQB.LoadJsonArray2(jaData: TJSONArray);
var
  jso: TJSONObject;
  i, j: integer;
  row: integer;
begin
  Memo1.Lines.Add( '---------------------------------------------------------------' );
  Memo1.Lines.Add( 'LoadJsonArray into asgData' );

  asgData2.ClearAll;
  asgData2.RowCount := 1;
  asgData2.StartUpdate;

  jso := TJSONObject(jaData.Items[0]);
  asgData2.ColCount := jso.Count;
  for i := 0 to jso.Count - 1 do
    asgData2.Cells[i+1, 0] := jso.Pairs[i].JsonString.Value;

  for i := 0 to jaData.Count - 1 do
  begin
    jso := TJSONObject(jaData.Items[i]);
    asgData2.RowCount := asgData2.RowCount + 1;
    row := asgData2.RowCount - 1;
    for j := 0 to jso.Count - 1 do
      asgData2.Cells[j+1, row] := jso.Pairs[j].JsonValue.Value;
  end;
  asgData2.EndUpdate;
  asgData2.AutoSizeColumns(true);
end;

procedure TfQB.Panel1Resize(Sender: TObject);
begin
  AsgData2.Width := Panel1.ClientWidth;
  AsgData2.Height := Panel1.ClientHeight;
end;

procedure TfQB.Panel2Resize(Sender: TObject);
begin
  AsgData.Width := Panel2.ClientWidth;
  AsgData.Height := Panel2.ClientHeight;
end;

procedure tfQB.findMatches(CustomerList: TJSONArray; JSONfield, DBField: string);
var
  I: integer;
  Customer: TJSONObject;
  SQL: string;
begin
  Memo1.Lines.Add('Matching ' + JSONfield + ' on ' + DBField);
  for I := 0 to CustomerList.Count - 1 do
  begin
    Customer := CustomerList.Items[I] as TJSONObject;

    SQL := 'select COUNT(*) as count from customers where NAME = ' + quotedStr(Customer.GetValue<string>(JSONfield));
    doQuery(ordersDB.UniQuery1, SQL);

    if ordersDB.UniQuery1.FieldByName('count').AsInteger = 1 then
    begin
      SQL := 'select * from customers where ' + DBField + ' = ' + quotedStr(Customer.GetValue<string>(JSONfield));
      doQuery(ordersDB.UniQuery1, SQL);
      Memo1.Lines.Add('Match at ' + ordersDB.UniQuery1.FieldByName('SHORT_NAME').AsString)
    end
    else if ordersDB.UniQuery1.FieldByName('count').AsInteger > 1 then
      Memo1.Lines.Add(ordersDB.UniQuery1.FieldByName('count').AsString + ' Matches at ' + Customer.GetValue<string>(JSONfield) + ' in field ' + JSONfield);
  end;
end;

end.
