Delphi Can

Orjinalini görmek için tıklayınız: Delphi İle Shopify Entegrasyonu
Şu anda (Arşiv) modunu görüntülemektesiniz. Orjinal Sürümü Görüntüle internal link
Merhabalar, uzun zamandır hayatımdaki artan tempo nedeniyle paylaşım yapmıyordum,. Shopify ile ürün entegrasyonu yapmam gerekti, birilerine de lazım olur diye buraya bırakıyorum. Saygılar.

unit ShopifyLib;

interface

 uses SysUtils, Classes, REST.Types, REST.Client, Data.Bind.Components, Data.Bind.ObjectScope, Soap.XSBuiltIns, Dialogs, superobject, Math, DateUtils;

 type TShopifyLogProc = procedure (const AHandle: Cardinal; const ADirName, AValue: String; const AMemoLog: Boolean);

 type
   TShopifyVariant = record
   product_id : String;
   id : String;
   title : String;
   price : Extended;
   position : Integer;
   sku : String; // Stock code
   inventory_quantity : Extended; // Envanter
 end;
 type TShopifyVariantList = Array of TShopifyVariant;

 type
   TShopifyProduct = record
   id : String;
   title : String;
   status : String;
   vendor : String;
   handle : String;
   created_at : TDateTime;
   updated_at : TDateTime;
   variants : TShopifyVariantList;
 end;
 type TShopifyProductList = Array of TShopifyProduct;

 type
   TNewOption = record
   name : String;
   values : Array of String;
 end;
 type TNewOptionList = Array of TNewOption;

 type
   TNewImages = record
   base64 : String;
 end;
 type TNewImagesList = Array of TNewImages;

 type
   TNewVariant = record
   option1 : String;
   option2 : String;
   option3 : String;
   title : String;
   price : Extended;
   barcode : String;
   sku : String;
   requires_shipping : Boolean;
   taxable : Boolean;
   inventory_management : String;
   inventory_policy : String;
   inventory_quantity : Extended;
   VariantImageSet : Boolean;
   images : TNewImagesList;
 end;
 type TNewVariantList = Array of TNewVariant;

 type
   TNewProduct = record
   title : String;
   body_html : String;
   published_scope : String;
   tags : String;
   status : String;
   vendor : String;
   shopifyProductId : String;
   options : TNewOptionList;
   variants : TNewVariantList;
 end;

 type
   TShopifyCollectionRules = record
   column : String;
   relation : String;
   condition : String;
 end;
 type TShopifyCollectionRulesList = Array of TShopifyCollectionRules;

 type
   TShopifyCollection = record
   id : String;
   title : String;
   rules : TShopifyCollectionRulesList;
 end;
 type TShopifyCollectionList = Array of TShopifyCollection;

 type
   TNewRules = record
   column : String;
   relation : String;
   condition : String;
 end;
 type TNewRulesList = Array of TNewRules;

 type
   TNewCollection = record
   title : String;
   shopifyCollectionId : String;
   imageBase64 : String;
   rules : TShopifyCollectionRulesList;
 end;

 type
   TNewCollectionResult = record
   id : String;
   errormessage : String;
 end;

 type
   TNewImagesResult = record
   id : String;
 end;
 type TNewImagesResultList = Array of TNewImagesResult;

 type
   TNewVariantResult = record
   id : String;
   sku : String;
 end;
 type TNewVariantResultList = Array of TNewVariantResult;

 type
   TNewProductResult = record
   id : String;
   errormessage : String;
   variants : TNewVariantResultList;
   images : TNewImagesResultList;
 end;

 type
 TShopify = class(TComponent)
 private
   FMainUrl: String;
   FVendor: String;
   FAccessToken: String;
   FRateLimitList : TStringList;
   procedure LogAdd(const AValue:String; const AMemo:Boolean);
   function UTCTimeToDateTime(const AUTCTime:String):TDateTime;
   function ShopifyStrToFloat(const AStr:String):Extended;
   function ShopifyFloatToStr(const AFloat:Extended):String;
   function ShopifyRequest(const AUrl,ARequestType,ABody:String;AHeaderName,AHeaderValue,AParamName,AParamValue:Array of String):String;
 public
   FLogFormHandle : Cardinal;
   FLogProc : TShopifyLogProc;
   constructor Create(AOwner: TComponent); override;
   destructor Destroy; override;

   function GetProductList_Id(const AProductId,AFields:String; var ASuccess:Boolean):TShopifyProductList;
   function DeleteProduct_Id(const AProductId:String):Boolean;
   function SetProduct(const AProduct:TNewProduct;var AProductJson,AResultJson:String):TNewProductResult;
   function UpdateVariantImage_Id(const AProductId,AImageId,AVariantId:String):Boolean;

   function GetCollectionList_Id(const ACollectId,AFields:String; var ASuccess:Boolean):TShopifyCollectionList;
   function DeleteCollection_Id(const ACollectId:String):Boolean;
   function SetCollection(const ACollection:TNewCollection;var ACollectionJson,AResultJson:String):TNewCollectionResult;
 published
   property MainUrl:String read FMainUrl write FMainUrl;
   property Vendor:String read FVendor write FVendor;
   property AccessToken:String read FAccessToken write FAccessToken;
 end;

implementation

{ TShopify }

constructor TShopify.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);
 FLogFormHandle := 0;
 FRateLimitList := TStringList.Create;
end;

function TShopify.DeleteCollection_Id(const ACollectId: String): Boolean;
var
 xGetJson : String;
begin
 Result := False;
 try
   xGetJson := ShopifyRequest(Format('/admin/api/2025-01/smart_collections/%s.json',[ACollectId]),'DELETE','',['X-Shopify-Access-Token'],[AccessToken],[],[]);
   Result := Copy(xGetJson,1,2) = 'OK';
   if not Result then
     LogAdd(Format('ProcessBig GrineleteCollection_Id Json:%s',[xGetJson]),True);
 except
   on e:Exception do
   begin
     LogAdd(Format('ProcessBig GrineleteCollection_Id Message:%s Json:%s',[e.Message,xGetJson]),True);
   end;
 end;
end;

function TShopify.DeleteProduct_Id(const AProductId: String): Boolean;
var
 xGetJson : String;
begin
 Result := False;
 try
   xGetJson := ShopifyRequest(Format('/admin/api/2025-01/products/%s.json',[AProductId]),'DELETE','',['X-Shopify-Access-Token'],[AccessToken],[],[]);
   Result := Copy(xGetJson,1,2) = 'OK';
   if not Result then
     LogAdd(Format('ProcessBig GrineleteProduct_Id Json:%s',[xGetJson]),True);
 except
   on e:Exception do
   begin
     LogAdd(Format('ProcessBig GrineleteProduct_Id Message:%s Json:%s',[e.Message,xGetJson]),True);
   end;
 end;
end;

destructor TShopify.Destroy;
begin
 FRateLimitList.Free;
 inherited;
end;

function TShopify.GetCollectionList_Id(const ACollectId, AFields: String; var ASuccess: Boolean): TShopifyCollectionList;
var
 xGetJson : String;
 xParamName,xParamValue : Array of String;

 Ind, Ind2 : Integer;
 xSO : ISuperObject;
 xSACollect : TSuperArray;
 xSARules : TSuperArray;
 xHighP, xHighV : Integer;
begin
 ASuccess := False;
 SetLength(Result,0);
 try
   if not ACollectId.Trim.Equals('')  then
   begin
     SetLength(xParamName,1);
     SetLength(xParamValue,1);
     xParamName[0] := 'ids';
     xParamValue[0] := ACollectId;
   end;

   if not AFields.Trim.Equals('')  then
   begin
     SetLength(xParamName,Length(xParamName)+1);
     SetLength(xParamValue,Length(xParamValue)+1);
     xParamName[Pred(Length(xParamName))] := 'fields';
     xParamValue[Pred(Length(xParamName))] := AFields;
   end;

   xGetJson := ShopifyRequest('/admin/api/2025-01/smart_collections.json','GET','',['X-Shopify-Access-Token'],[AccessToken],xParamName,xParamValue);

   if Copy(xGetJson,1,2) = 'OK' then
   begin
     xGetJson := Copy(xGetJson,4,MaxInt);
     xSO := SO(xGetJson);
     if xSO <> nil then
     begin
       ASuccess := True;
       {$REGION 'Products'}
         xSACollect := xSO.A['smart_collections'];
         for Ind := 0 to Pred(xSACollect.Length) do
         begin
           SetLength(Result,Succ(Length(Result)));
           xHighP := Pred(Length(Result));
           with Result[xHighP] do
           begin
             id := xSACollect[Ind].S['id'];
             title := xSACollect[Ind].S['title'];
           end;
           if xSACollect[Ind].O['rules'] <> nil then
           begin
             {$REGION 'rules'}
               xSARules := xSACollect[Ind].A['rules'];
               for Ind2 := 0 to Pred(xSARules.Length) do
               begin
                 SetLength(Result[xHighP].rules,Succ(Length(Result[xHighP].rules)));
                 xHighV := Pred(Length(Result[xHighP].rules));
                 with Result[xHighP].rules[xHighV] do
                 begin
                   column := xSARules[Ind2].S['column'];
                   relation := xSARules[Ind2].S['relation'];
                   condition := xSARules[Ind2].S['condition'];
                 end;
               end;
             {$ENDREGION}
           end;
         end;
       {$ENDREGION}
     end
     else
       LogAdd(Format('Process:GetCollectionList_Id JsonParseError:%s',[xGetJson]),True);
   end
   else
     LogAdd(Format('Process:GetCollectionList_Id Json:%s',[xGetJson]),True);
 except
   on e:Exception do
   begin
     LogAdd(Format('Process:GetCollectionList_Id Message:%s Json:%s',[e.Message,xGetJson]),True);
   end;
 end;
end;

function TShopify.GetProductList_Id(const AProductId, AFields: String; var ASuccess:Boolean): TShopifyProductList;
var
 xGetJson : String;
 xParamName,xParamValue : Array of String;

 Ind, Ind2 : Integer;
 xSO : ISuperObject;
 xSAProducts : TSuperArray;
 xSAVariants : TSuperArray;
 xHighP, xHighV : Integer;
begin
 ASuccess := False;
 SetLength(Result,0);
 try
   if not AProductId.Trim.Equals('')  then
   begin
     SetLength(xParamName,1);
     SetLength(xParamValue,1);
     xParamName[0] := 'ids';
     xParamValue[0] := AProductId;
   end;

   if not AFields.Trim.Equals('')  then
   begin
     SetLength(xParamName,Length(xParamName)+1);
     SetLength(xParamValue,Length(xParamValue)+1);
     xParamName[Pred(Length(xParamName))] := 'fields';
     xParamValue[Pred(Length(xParamName))] := AFields;
   end;

   xGetJson := ShopifyRequest('/admin/api/2025-01/products.json','GET','',['X-Shopify-Access-Token'],[AccessToken],xParamName,xParamValue);

   if Copy(xGetJson,1,2) = 'OK' then
   begin
     xGetJson := Copy(xGetJson,4,MaxInt);
     xSO := SO(xGetJson);
     if xSO <> nil then
     begin
       ASuccess := True;
       {$REGION 'Products'}
         xSAProducts := xSO.A['products'];
         for Ind := 0 to Pred(xSAProducts.Length) do
         begin
           SetLength(Result,Succ(Length(Result)));
           xHighP := Pred(Length(Result));
           with Result[xHighP] do
           begin
             id := xSAProducts[Ind].S['id'];
             title := xSAProducts[Ind].S['title'];
             status := xSAProducts[Ind].S['status'];
             vendor := xSAProducts[Ind].S['vendor'];
             handle := xSAProducts[Ind].S['handle'];
             if not xSAProducts[Ind].S['created_at'].Trim.Equals('') then
               created_at := UTCTimeToDateTime(xSAProducts[Ind].S['created_at']);
             if not xSAProducts[Ind].S['updated_at'].Trim.Equals('') then
               updated_at := UTCTimeToDateTime(xSAProducts[Ind].S['updated_at']);
           end;
           if xSAProducts[Ind].O['variants'] <> nil then
           begin
             {$REGION 'Variants'}
               xSAVariants := xSAProducts[Ind].A['variants'];
               for Ind2 := 0 to Pred(xSAVariants.Length) do
               begin
                 SetLength(Result[xHighP].variants,Succ(Length(Result[xHighP].variants)));
                 xHighV := Pred(Length(Result[xHighP].variants));
                 with Result[xHighP].variants[xHighV] do
                 begin
                   product_id := xSAVariants[Ind2].S['product_id'];
                   id := xSAVariants[Ind2].S['id'];
                   title := xSAVariants[Ind2].S['title'];
                   price := ShopifyStrToFloat(xSAVariants[Ind2].S['price']);
                   position := xSAVariants[Ind2].I['position'];
                   sku := xSAVariants[Ind2].S['sku'];
                   inventory_quantity := xSAVariants[Ind2].D['inventory_quantity'];
                 end;
               end;
             {$ENDREGION}
           end;
         end;
       {$ENDREGION}
     end
     else
       LogAdd(Format('Process:GetProductList_Id JsonParseError:%s',[xGetJson]),True);
   end
   else
     LogAdd(Format('Process:GetProductList_Id Json:%s',[xGetJson]),True);
 except
   on e:Exception do
   begin
     LogAdd(Format('Process:GetProductList_Id Message:%s Json:%s',[e.Message,xGetJson]),True);
   end;
 end;
end;

procedure TShopify.LogAdd(const AValue: String; const AMemo: Boolean);
begin
 if (FLogFormHandle > 0) and Assigned(FLogProc) then
 begin
   FLogProc(FLogFormHandle,'ShopifyLibLog',AValue,AMemo);
 end;
end;

function TShopify.SetCollection(const ACollection: TNewCollection; var ACollectionJson, AResultJson: String): TNewCollectionResult;
var
 Ind : Integer;
 xUrl,xMethod : String;
 xUpdate : Boolean;
 xGetJson : String;
 GccSuccess : Boolean;

 xSOMain, xSOCollect : ISuperObject;
 xSARules : TSuperArray;
 xSOTmpItem : ISuperObject;

 xSOResult : ISuperObject;
begin
 Result.id := '';
 Result.errormessage := '';
 try
   xUpdate := False;
   if not ACollection.shopifyCollectionId.Trim.Equals('') then
   begin
     xUpdate := Length(GetCollectionList_Id(ACollection.shopifyCollectionId,'id',GccSuccess)) > 0;
     if not GccSuccess then
     begin
       Result.errormessage := Format('Process:SetCollection update collection->GetCollectionList_Id get error!',[]);
       LogAdd(Result.errormessage,True);
       Exit;
     end;
   end;

   if not xUpdate then
   begin
     xUrl := '/admin/api/2025-01/smart_collections.json';
     xMethod := 'POST';
   end
   else
   begin
     xUrl := Format('/admin/api/2025-01/smart_collections/%s.json',[ACollection.shopifyCollectionId]);
     xMethod := 'PUT';
   end;

   xSOMain := SO();
   xSOMain.O['smart_collection'] := SO();
   xSOCollect := xSOMain.O['smart_collection'];

   xSOCollect.S['title'] := ACollection.title;
   xSOCollect.B['disjunctive'] := True; // any condition

   if Length(ACollection.rules) > 0 then
   begin
     xSOCollect.O['rules'] := SA([]);
     xSARules := xSOCollect.A['rules'];
     for Ind := Low(ACollection.rules) to High(ACollection.rules) do
     begin
       xSOTmpItem := SO();
       xSOTmpItem.S['column'] := ACollection.rules[Ind].column;
       xSOTmpItem.S['relation'] := ACollection.rules[Ind].relation;
       xSOTmpItem.S['condition'] := ACollection.rules[Ind].condition;

       xSARules.Add(xSOTmpItem);
     end;
   end;

   if not ACollection.imageBase64.Trim.Equals('') then
   begin
     xSOMain.O['image'] := SO();
     xSOMain.O['image'].S['attachment'] := ACollection.imageBase64;
   end;

   xGetJson := ShopifyRequest(xUrl,xMethod,xSOMain.AsJSon(),['X-Shopify-Access-Token'],[AccessToken],[],[]);
   ACollectionJson := xSOMain.AsJSon();
   AResultJson := xGetJson;

   if Copy(xGetJson,1,2) = 'OK' then
   begin
     xGetJson := Copy(xGetJson,4,MaxInt);
     xSOResult := SO(xGetJson);
     if xSOResult <> nil then
     begin
       Result.id := xSOResult.O['smart_collection'].S['id'];
     end
     else
     begin
       Result.errormessage := Format('Process:SetCollection JsonParseError:%s',[xGetJson]);
       LogAdd(Result.errormessage,True);
     end;
   end
   else
   begin
     Result.errormessage := Format('Process:SetCollection Json:%s',[xGetJson]);
     LogAdd(Result.errormessage,True);
   end;
 except
   on e:Exception do
   begin
     Result.errormessage := Format('Process:SetCollection Message:%s Json:%s',[e.Message,xGetJson]);
     LogAdd(Result.errormessage,True);
   end;
 end;
end;

function TShopify.SetProduct(const AProduct: TNewProduct; var AProductJson, AResultJson: String): TNewProductResult;
var
 Ind,Ind2,Ind3 : Integer;
 xUrl,xMethod : String;
 xUpdate : Boolean;
 xGetJson : String;
 CountIndex, GccIndex : Integer;
 GccSuccess : Boolean;

 xSOMain, xSOProduct : ISuperObject;
 xSAOptions, xSAVariants, xSAImages : TSuperArray;
 xSOTmpItem : ISuperObject;

 xSOResult : ISuperObject;
begin
 Result.id := '';
 Result.errormessage := '';
 SetLength(Result.variants,0);

 try
   xUpdate := False;
   if not AProduct.shopifyProductId.Trim.Equals('') then
   begin
     xUpdate := Length(GetProductList_Id(AProduct.shopifyProductId,'id',GccSuccess)) > 0;
     if not GccSuccess then
     begin
       Result.errormessage := Format('Process:SetProduct update product->GetProductList_Id get error!',[]);
       LogAdd(Result.errormessage,True);
       Exit;
     end;
   end;

   if not xUpdate then
   begin
     xUrl := '/admin/api/2025-01/products.json';
     xMethod := 'POST';
   end
   else
   begin
     xUrl := Format('/admin/api/2025-01/products/%s.json',[AProduct.shopifyProductId]);
     xMethod := 'PUT';
   end;

   xSOMain := SO();
   xSOMain.O['product'] := SO();
   xSOProduct := xSOMain.O['product'];

   xSOProduct.S['title'] := AProduct.title;
   xSOProduct.S['body_html'] := AProduct.body_html;
   xSOProduct.S['published_scope'] := AProduct.published_scope;
   xSOProduct.S['tags'] := AProduct.tags;
   xSOProduct.S['status'] := AProduct.status;
   xSOProduct.S['vendor'] := AProduct.vendor;

   if Length(AProduct.options) > 0 then
   begin
     xSOProduct.O['options'] := SA([]);
     xSAOptions := xSOProduct.A['options'];
     for Ind := Low(AProduct.options) to High(AProduct.options) do
     begin
       xSOTmpItem := SO();
       xSOTmpItem.S['name'] := AProduct.options[Ind].name;

       xSOTmpItem.O['values'] := SA([]);
       for Ind2 := Low(AProduct.options[Ind].values) to High(AProduct.options[Ind].values) do
         xSOTmpItem.A['values'].Add(AProduct.options[Ind].values[Ind2]);

       xSAOptions.Add(xSOTmpItem);
     end;
   end;

   xSOProduct.O['variants'] := SA([]);
   xSAVariants := xSOProduct.A['variants'];
   for Ind := Low(AProduct.variants) to High(AProduct.variants) do
   begin
     xSOTmpItem := SO();

     if not AProduct.variants[Ind].option1.Trim.Equals('') then
       xSOTmpItem.S['option1'] := AProduct.variants[Ind].option1
     else
       xSOTmpItem.O['option1'] := TSuperObject.Create(stNull);
     if not AProduct.variants[Ind].option2.Trim.Equals('') then
       xSOTmpItem.S['option2'] := AProduct.variants[Ind].option2
     else
       xSOTmpItem.O['option2'] := TSuperObject.Create(stNull);
     if not AProduct.variants[Ind].option3.Trim.Equals('') then
       xSOTmpItem.S['option3'] := AProduct.variants[Ind].option3
     else
       xSOTmpItem.O['option3'] := TSuperObject.Create(stNull);

     xSOTmpItem.S['title'] :=  AProduct.variants[Ind].title;
     xSOTmpItem.S['price'] :=  ShopifyFloatToStr(AProduct.variants[Ind].price);
     xSOTmpItem.S['barcode'] :=  AProduct.variants[Ind].barcode;
     xSOTmpItem.B['requires_shipping'] :=  AProduct.variants[Ind].requires_shipping;
     xSOTmpItem.B['taxable'] :=  AProduct.variants[Ind].taxable;
     xSOTmpItem.S['sku'] :=  AProduct.variants[Ind].sku;
     xSOTmpItem.S['inventory_management'] :=  AProduct.variants[Ind].inventory_management;
     xSOTmpItem.S['inventory_policy'] :=  AProduct.variants[Ind].inventory_policy;
     xSOTmpItem.I['inventory_quantity'] :=  Trunc(AProduct.variants[Ind].inventory_quantity);

     xSAVariants.Add(xSOTmpItem);
   end;

   xSOProduct.O['images'] := SA([]);
   xSAImages := xSOProduct.A['images'];
   for Ind := Low(AProduct.variants) to High(AProduct.variants) do
   begin
     for Ind2 := Low(AProduct.variants[Ind].images) to High(AProduct.variants[Ind].images) do
     begin
       xSOTmpItem := SO();
       xSOTmpItem.S['attachment'] := AProduct.variants[Ind].images[Ind2].base64;
       xSAImages.Add(xSOTmpItem);
     end;
   end;

   xGetJson := ShopifyRequest(xUrl,xMethod,xSOMain.AsJSon(),['X-Shopify-Access-Token'],[AccessToken],[],[]);
   AProductJson := xSOMain.AsJSon();
   AResultJson := xGetJson;

   if Copy(xGetJson,1,2) = 'OK' then
   begin
     xGetJson := Copy(xGetJson,4,MaxInt);
     xSOResult := SO(xGetJson);
     if xSOResult <> nil then
     begin
       Result.id := xSOResult.O['product'].S['id'];
       if xSOResult.O['product'].O['variants'] <> nil then
       begin
         SetLength(Result.variants,xSOResult.O['product'].A['variants'].Length);
         for Ind := 0 to Pred(xSOResult.O['product'].A['variants'].Length) do
         begin
           Result.variants[Ind].id := xSOResult.O['product'].A['variants'][Ind].S['id'];
           Result.variants[Ind].sku := xSOResult.O['product'].A['variants'][Ind].S['sku'];
         end;
       end;

       if xSOResult.O['product'].O['images'] <> nil then
       begin
         SetLength(Result.images,xSOResult.O['product'].A['images'].Length);
         for Ind := 0 to Pred(xSOResult.O['product'].A['images'].Length) do
         begin
           Result.images[Ind].id := xSOResult.O['product'].A['images'][Ind].S['id'];
         end;
       end;

       {$REGION 'Variant First Image Set'}
         for Ind := Low(AProduct.variants) to High(AProduct.variants) do
         begin
           if AProduct.variants[Ind].VariantImageSet and (Length(AProduct.variants[Ind].images) > 0) and  (not AProduct.variants[Ind].sku.Trim.Equals('')) then
           begin
             CountIndex := -1;
             GccIndex := -1;
             for Ind2 := Low(AProduct.variants) to High(AProduct.variants) do
             begin
               if GccIndex > -1 then
                 Break;
               for Ind3 := Low(AProduct.variants[Ind2].images) to High(AProduct.variants[Ind2].images) do
               begin
                 Inc(CountIndex);
                 if AProduct.variants[Ind].sku = AProduct.variants[Ind2].sku then
                 begin
                   GccIndex := CountIndex;
                   Break;
                 end;
               end;
             end;

             if GccIndex > -1 then
             begin
               if High(Result.images)>=GccIndex  then
               begin
                 UpdateVariantImage_Id(Result.id,Result.images[GccIndex].id,Result.variants[Ind].id);
               end;
             end;
           end;
         end;
       {$ENDREGION}
     end
     else
     begin
       Result.errormessage := Format('Process:SetProduct JsonParseError:%s',[xGetJson]);
       LogAdd(Result.errormessage,True);
     end;
   end
   else
   begin
     Result.errormessage := Format('Process:SetProduct Json:%s',[xGetJson]);
     LogAdd(Result.errormessage,True);
   end;
 except
   on e:Exception do
   begin
     Result.errormessage := Format('Process:SetProduct Message:%s Json:%s',[e.Message,xGetJson]);
     LogAdd(Result.errormessage,True);
   end;
 end;
end;

function TShopify.ShopifyFloatToStr(const AFloat: Extended): String;
begin
 Result := StringReplace(FloatToStr(SimpleRoundTo(AFloat,-2)),',','.',[rfReplaceAll]);
end;

function TShopify.ShopifyRequest(const AUrl, ARequestType, ABody: String; AHeaderName, AHeaderValue, AParamName, AParamValue: array of String): String;
var
 GccDateTime : TDateTime;
 SecondReqCount : Integer;
 Ind : Integer;
 xRESTClient: TRESTClient;
 xRESTRequest: TRESTRequest;
 xRESTResponse: TRESTResponse;
begin
 {$REGION 'Rate Limit'}
   // 1 second max 2 req or 60 second max 40 req

   while True do
   begin
     SecondReqCount := 0;
     for Ind := FRateLimitList.Count-1 downto 0 do
     begin
       GccDateTime := StrToDateTime(FRateLimitList[Ind]);

       if MilliSecondsBetween(Now,GccDateTime) <= 1000 then
         Inc(SecondReqCount);

       if MilliSecondsBetween(Now,StrToDateTime(FRateLimitList[Ind])) > 60000 then
         FRateLimitList.Delete(Ind);
     end;

     if (SecondReqCount>=2) or (FRateLimitList.Count>=40) then
       Sleep(1000)
     else
       Break;
   end;
 {$ENDREGION}

 Result := '';
 try
   xRESTClient := TRESTClient.Create(nil);
   xRESTResponse := TRESTResponse.Create(nil);
   xRESTRequest := TRESTRequest.Create(nil);
   try
     with xRESTClient do
     begin
       BaseURL := FMainUrl + AUrl;
       ConnectTimeout := 10000;
       ReadTimeout := 30000;
     end;
     with xRESTRequest do
     begin
       AssignedValues := [TAssignedValue.rvConnectTimeout, TAssignedValue.rvReadTimeout];
       Client := xRESTClient;
       Response := xRESTResponse;
       ConnectTimeout := 10000;
       ReadTimeout := 30000;
       if ARequestType = 'POST' then
       begin
         Method := rmPOST;
         if ABody.Trim <> '' then
           Params.AddItem('BodyPost',ABody, TRESTRequestParameterKind.pkREQUESTBODY, [], TRESTContentType.ctAPPLICATION_JSON);
       end
       else if ARequestType = 'PUT' then
       begin
         Method := rmPUT;
         if ABody.Trim <> '' then
           Params.AddItem('BodyPost',ABody, TRESTRequestParameterKind.pkREQUESTBODY, [], TRESTContentType.ctAPPLICATION_JSON);
       end
       else if ARequestType = 'GET' then
         Method := rmGET
       else if ARequestType = 'DELETE' then
         Method := rmDELETE;

       for Ind := Low(AHeaderName) to High(AHeaderName) do
         Params.AddHeader(AHeaderName[Ind],AHeaderValue[Ind]);

       for Ind := Low(AParamName) to High(AParamName) do
         Params.AddItem(AParamName[Ind],AParamValue[Ind],pkGETorPOST,[]);
     end;

     FRateLimitList.Add(DateTimeToStr(Now));
     xRESTRequest.Execute;
     if xRESTResponse.StatusCode in [200,201] then
       Result := 'OK=' + xRESTResponse.Content
     else
       Result := 'ERROR=' + Format('Process:ShopifyRequest Url:%s, Method:%s, Status:%d, Content:%s',[AUrl,ARequestType,xRESTResponse.StatusCode,xRESTResponse.Content]);
   finally
     FreeAndNil(xRESTRequest);
     FreeAndNil(xRESTResponse);
     FreeAndNil(xRESTClient);
   end;
 except
   on e:Exception do
   begin
     Result := 'ERROR=' + Format('Process:ShopifyRequest Url:%s, Method:%s, Message:%s',[AUrl,ARequestType,e.Message]);
   end;
 end;
end;

function TShopify.ShopifyStrToFloat(const AStr: String): Extended;
begin
 try
   Result := StrToFloatDef(StringReplace(AStr,'.',FormatSettings.DecimalSeparator,[rfReplaceAll]),0);
 except
   Result := 0;
 end;
end;

function TShopify.UpdateVariantImage_Id(const AProductId, AImageId, AVariantId: String): Boolean;
var
 xGetJson : String;
 xSO : ISuperObject;
begin
 Result := False;
 try
   xSO := SO();
   xSO.O['image'] := SO();
   xSO.O['image'].O['variant_ids'] := SA([]);
   xSO.O['image'].A['variant_ids'].Add(AVariantId);

   xGetJson := ShopifyRequest(Format('//admin/api/2025-01/products/%s/images/%s.json',[AProductId,AImageId]),'PUT',xSO.AsJSon(),['X-Shopify-Access-Token'],[AccessToken],[],[]);
   Result := Copy(xGetJson,1,2) = 'OK';
   if not Result then
     LogAdd(Format('Process:UpdateVariantImage_Id Json:%s',[xGetJson]),True);
 except
   on e:Exception do
   begin
     LogAdd(Format('Process:UpdateVariantImage_Id Message:%s Json:%s',[e.Message,xGetJson]),True);
   end;
 end;
end;


function TShopify.UTCTimeToDateTime(const AUTCTime: String): TDateTime;
var
 xDateTime : TXSDateTime;
begin
 try
   xDateTime := TXSDateTime.Create;
   try
     xDateTime.XSToNative(AUTCTime);
     Result := xDateTime.AsDateTime;
   finally
     FreeAndNil(xDateTime);
   end;
 except
   Result := 0;
 end;
end;

end.

Kullanım Örneği Stok Gönderimi;
procedure TForm4.Button3Click(Sender: TObject);
var
 Ind : Integer;
 xShopify : TShopify;
 xProduct : TNewProduct;
 xResult : TNewProductResult;
 xProductJson, xResponseJson : String;
begin
 Memo1.Lines.Clear;
 xShopify := TShopify.Create(Self);
 try
   xShopify.MainUrl := 'https://xxx.myshopify.com';
   xShopify.Vendor := 'MyFirm';
   xShopify.AccessToken := 'xxx';

   xProduct.title := 'Megane 2 Oksijen Sensörü';
   xProduct.body_html := '<strong> Açıklama Test</strong>';
   xProduct.published_scope := 'global';
   xProduct.tags := 'Renault, Megane';
   xProduct.status := 'active';
   xProduct.vendor := 'MyFirm';
   xProduct.shopifyProductId := '7916363153468';

   SetLength(xProduct.options,2);
     xProduct.options[0].name := 'Marka';
       SetLength(xProduct.options[0].values,2);
       xProduct.options[0].values[0] := 'Bosch';
       xProduct.options[0].values[1] := 'KrofWall';
     xProduct.options[1].name := 'Pin';
       SetLength(xProduct.options[1].values,2);
       xProduct.options[1].values[0] := '3 Pin';
       xProduct.options[1].values[1] := '4 Pin';

   SetLength(xProduct.variants,2);
     xProduct.variants[0].option1 := 'Bosch';
     xProduct.variants[0].option2 := '4 Pin';
     xProduct.variants[0].title := 'Ürün A';
     xProduct.variants[0].price := 1499.99;
     xProduct.variants[0].barcode := '';
     xProduct.variants[0].sku := 'stok_kodu1';
     xProduct.variants[0].requires_shipping := True;
     xProduct.variants[0].taxable := True;
     xProduct.variants[0].inventory_management := 'shopify';
     xProduct.variants[0].inventory_policy := 'continue';
     xProduct.variants[0].inventory_quantity := 5;
     xProduct.variants[0].VariantImageSet := True;
     SetLength(xProduct.variants[0].images,1);
     xProduct.variants[0].images[0].base64 := Image1Text;

     xProduct.variants[1].option1 := 'KrofWall';
     xProduct.variants[1].option2 := '4 Pin';
     xProduct.variants[1].title := 'Ürün B';
     xProduct.variants[1].price := 1099;
     xProduct.variants[1].barcode := '';
     xProduct.variants[1].sku := 'stok_kodu2';
     xProduct.variants[1].requires_shipping := True;
     xProduct.variants[1].taxable := True;
     xProduct.variants[1].inventory_management := 'shopify';
     xProduct.variants[1].inventory_policy := 'continue';
     xProduct.variants[1].inventory_quantity := 10;
     xProduct.variants[1].VariantImageSet := True;
     SetLength(xProduct.variants[1].images,1);
     xProduct.variants[1].images[0].base64 := Image2Text;

     xResult := xShopify.SetProduct(xProduct,xProductJson,xResponseJson);

//    ShowMessage('ProductJson' + sLineBreak + xProductJson);
//    ShowMessage('ResponseJson' + sLineBreak + xResponseJson);

   Memo1.Lines.Add(Format('productid: %s',[xResult.id]));
   for Ind := Low(xResult.variants) to High(xResult.variants) do
     Memo1.Lines.Add(Format('---> variantid: %s, variantsku: %s',[xResult.variants[Ind].id,xResult.variants[Ind].sku]));
 finally
   xShopify.Free;
 end;
end;

Kullanım Örneği ÜrünListesi;
procedure TForm4.Button1Click(Sender: TObject);
var
 Ind,Ind2 : Integer;
 GccSuccess : Boolean;

 xShopify : TShopify;
 xProducts : TShopifyProductList;
begin
 Memo1.Lines.Clear;
 xShopify := TShopify.Create(Self);
 try
   xShopify.MainUrl := 'https://xxx.myshopify.com';
   xShopify.Vendor := 'MyFirm';
   xShopify.AccessToken := 'xxx';

   xProducts := xShopify.GetProductList_Id('7914936467516','',GccSuccess);
   for Ind := Low(xProducts) to High(xProducts) do
   begin
       Memo1.Lines.Add
       (
         Format(
           'id:%s, title:%s'
           ,[xProducts[Ind].id,xProducts[Ind].title]
               )
       );

     for Ind2 := Low(xProducts[Ind].variants) to High(xProducts[Ind].variants) do
       Memo1.Lines.Add
       (
         Format(
           '--->variant id:%s, sku:%s, title:%s, price:%s'
           ,[xProducts[Ind].variants[Ind2].id, xProducts[Ind].variants[Ind2].sku
             ,xProducts[Ind].variants[Ind2].title, FloatToStr(xProducts[Ind].variants[Ind2].price)
             ]
               )
       );
   end;

 finally
   xShopify.Free;
 end;
end;
Teşekkürler elinize sağlık