unit QBServiceImplementation;

interface

uses
   XData.Server.Module,
  XData.Service.Common,
  Api.Database, Data.DB, frxClass, frxExportPDF, JS, System.Hash, System.JSON,
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, MemDS, DBAccess, Uni,
  hyiedefs, hyieutils, iexBitmaps, iesettings, iexLayers, iexRulers,
  iexToolbars, iexUserInteractions, imageenio, imageenproc, QuickRpt, QRCtrls,
  dbimageen, Vcl.ExtCtrls, ieview, imageenview, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdExplicitTLSClientServerBase, IdFTP,
  iexProcEffects, frCoreClasses, Common.Logging,
  DateUtils, QBService,  WEBLib.REST, WEBLib.WebTools,System.Net.HttpClient,
  System.Net.URLClient, System.Net.HttpClientComponent, System.netencoding,
  IdHTTP, IdSSLOpenSSL, IdSSLOpenSSLHeaders, System.IniFiles, REST.Client, REST.Types;

type
  [ServiceImplementation]
  TQBService = class(TInterfacedObject, IQBService)
  private
    procedure SaveTokens(AccessToken, RefreshToken: string);
    function getCustomers(): TJSONArray;
    function refreshAccessToken(): string;
    var
      AccessToken,RefreshToken,CompanyID,Client,Secret: string;
      LastRefresh: TDateTime;
  end;

implementation

function TQBService.getCustomers: TJSONArray;
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;

  jsValue := restResponse.JSONValue;

  jsObj := TJSONObject(jsValue);

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

  result := CustomerList;

 // LoadJSONArray( CustomerList );

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

end;

function TQBService.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
    Exit();
  end;

  EncodedAuth := Encoder.Encode(Client + ':' + Secret);
  if RefreshToken = '' then
  begin
    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;
      except
        on E: EIdHTTPProtocolException do
         // Memo2.Lines.Add('Error: ' + E.Message + #13#10 + 'Response: ' + E.ErrorMessage);
      end;
    finally
      RequestStream.Free;
    end;
  finally
    SSLIO.Free;
    IdHTTP.Free;
  end;
end;

procedure TQBService.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;

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


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

initialization
  RegisterServiceType(TQBService);

end.
