简体   繁体   中英

Delphi REST GET but not POST works with AusPost eParcel API

I'm working on an integration project for a client that requires creating AusPost shipments using the eParcel REST API from a Delphi 10.2 Tokyo service program.

Here is what I have as the Constructor for my class:

constructor TAusPostRESTServices.Create;
begin
  // Create the REST objects

  HTTPBasicAuthenticator := THTTPBasicAuthenticator.Create(API_KEY, API_PASSWORD);
  RESTClient := TRESTClient.Create(BASE_URL);
  RESTResponse := TRESTResponse.Create(nil);
  RESTRequest := TRESTRequest.Create(nil);

  // Set the initial properties

  RESTClient.Params.Clear;
  RESTClient.HandleRedirects := true;
  RESTClient.Authenticator := HTTPBasicAuthenticator;
  RESTClient.RaiseExceptionOn500 := false;

  RESTRequest.Client := RESTClient;
  RESTRequest.Response := RESTResponse;
  RESTRequest.SynchronizedEvents := false;
end;

And the constants it uses (note that the account and API values are from the reference API and probably don't actually work).

const
  BASE_URL = 'https://digitalapi.auspost.com.au/test';
  ACCOUNT = '0000123456';
  API_KEY = '601a4032-6dbd-46aa-9c6c-8c6dacca5e61';
  API_PASSWORD = 'password';

The Uses clause has at least these:

REST.Client, REST.Authenticator.Basic, IPPeerClient, JSON, REST.Types

The eParcel API methods I'm trying to use are Create Shipments and Get Shipments .

Here is what my Get Shipments Delphi code looks like (Don't worry about the AJSONValue response. Still working on what that will do eventually):

function TAusPostRESTServices.GetShipments(var AError: String): Boolean;
var
  AStatusCode: Integer;
  AJSONString: String;
  AJSONValue: TJSonValue;
  AParameter: TRESTRequestParameter;
begin
  // Set the request header info

  RESTRequest.Method := TRESTRequestMethod.rmGET;
  RESTRequest.Resource := '/shipping/v1/shipments';
  RESTRequest.Accept := 'application/json';

  AParameter := RESTRequest.Params.AddItem;
  AParameter.ContentType := ctAPPLICATION_JSON;
  AParameter.name := 'Content-Type';
  AParameter.Value := 'application/json';
  AParameter.Kind := TRESTRequestParameterKind.pkHTTPHEADER;

  RESTRequest.Params.AddHeader('account-number', ACCOUNT);

  try
    try
      RESTRequest.Execute;
    except on E: Exception do
      ;
    end;

    // If the StatusCode is 200, then the REST method worked

    AStatusCode := RESTRequest.Response.StatusCode;

    if AStatusCode <> 200 then
      AError := RESTRequest.Response.Content
    else
    begin
      AJSONValue := TJSonObject.ParseJSONValue(RESTRequest.Response.Content);

      AJSONValue.Free;

      AError := '';
    end;
  finally
    Result := (AError = '');
  end;
end;

This works fine and returns the one trial demo shipment I created using Postman, from the sample JSON included in the AusPost API documentation.

{
  "shipments": [
    {
      "shipment_id": "T7sK0EA9HGcAAAFufLYgUxkK",
      "shipment_reference": "My second shipment ref",
      "shipment_creation_date": "2019-10-30T06:42:42+11:00",
      "customer_reference_1": "cb1234",
      "customer_reference_2": "cb2345",
      "sender_references": [
        "cb1234",
        "cb2345"
      ],
      "from": {
        "type": "MERCHANT_LOCATION",
        "lines": [
          "111 Bourke St"
        ],
        "suburb": "Melbourne",
        "postcode": "3000",
        "state": "VIC",
        "apcn": "2468013579",
        "name": "Carl Block",
        "country": "AU"
      },
      "to": {
        "type": "STANDARD_ADDRESS",
        "lines": [
          "PO Box 123"
        ],
        "suburb": "Rye",
        "postcode": "3941",
        "state": "VIC",
        "apcn": "1234567890",
        "name": "Blocked Carl",
        "business_name": "In debt",
        "country": "AU",
        "email": "carl@gmai.co",
        "phone": "0356567567",
        "delivery_instructions": "please leave behind shed"
      },
      "items": [
        {
          "weight": 1,
          "height": 10,
          "length": 10,
          "width": 10,
          "contains_dangerous_goods": false,
          "authority_to_leave": true,
          "safe_drop_enabled": true,
          "allow_partial_delivery": false,
          "item_id": "3mQK0EA9CksAAAFui7YgUxkK",
          "item_reference": "blocked",
          "tracking_details": {
            "article_id": "111JD538925401000930800",
            "consignment_id": "111JD5389254"
          },
          "product_id": "7E55",
          "item_summary": {
            "total_cost": 11.48,
            "total_cost_ex_gst": 11.48,
            "total_gst": 0,
            "status": "Created"
          },
          "item_contents": [],
          "label": {
            "status": "Pending",
            "errors": []
          },
          "postage_details": {
            "price": {
              "calculated_price": 11.48,
              "calculated_price_ex_gst": 11.48,
              "calculated_gst": 0
            }
          }
        },
        {
          "weight": 2.123,
          "height": 10,
          "length": 100.1,
          "width": 10,
          "contains_dangerous_goods": false,
          "authority_to_leave": true,
          "safe_drop_enabled": true,
          "allow_partial_delivery": false,
          "item_id": "9cgK0EA9smIAAAFufrYgUxkK",
          "item_reference": "blocked",
          "tracking_details": {
            "article_id": "111JD538925403000930804",
            "consignment_id": "111JD5389254"
          },
          "product_id": "7E55",
          "item_summary": {
            "total_cost": 23.34,
            "total_cost_ex_gst": 23.34,
            "total_gst": 0,
            "status": "Created"
          },
          "item_contents": [],
          "label": {
            "status": "Pending",
            "errors": []
          },
          "postage_details": {
            "price": {
              "calculated_price": 23.34,
              "calculated_price_ex_gst": 23.34,
              "calculated_gst": 0
            }
          }
        },
        {
          "weight": 1,
          "height": 10,
          "length": 10,
          "width": 10,
          "contains_dangerous_goods": false,
          "authority_to_leave": true,
          "safe_drop_enabled": true,
          "allow_partial_delivery": false,
          "item_id": "_hEK0EA9zIIAAAFuirYgUxkK",
          "item_reference": "blocked",
          "tracking_details": {
            "article_id": "111JD538925402000930807",
            "consignment_id": "111JD5389254"
          },
          "product_id": "7E55",
          "item_summary": {
            "total_cost": 11.48,
            "total_cost_ex_gst": 11.48,
            "total_gst": 0,
            "status": "Created"
          },
          "item_contents": [],
          "label": {
            "status": "Pending",
            "errors": []
          },
          "postage_details": {
            "price": {
              "calculated_price": 11.48,
              "calculated_price_ex_gst": 11.48,
              "calculated_gst": 0
            }
          }
        }
      ],
      "options": {},
      "shipment_summary": {
        "total_cost": 47.41,
        "total_cost_ex_gst": 43.1,
        "fuel_surcharge": 1.11,
        "total_gst": 4.31,
        "status": "Created",
        "tracking_summary": {
          "Created": 3
        },
        "number_of_items": 3
      },
      "movement_type": "DESPATCH",
      "charge_to_account": "1001746337"
    }
  ],
  "pagination": {
    "total_number_of_records": 1,
    "number_of_records_per_page": 1000,
    "current_page_number": 1
  }
}

My Create Shipment is almost identical:

function TAusPostRESTServices.CreateShipment(var AError: String): Boolean;
var
  AStatusCode: Integer;
  AJSONString: String;
  AJSONValue: TJSonValue;
  AParameter: TRESTRequestParameter;
begin
  // Set the request header info

  RESTRequest.Method := TRESTRequestMethod.rmPOST;
  RESTRequest.Resource := '/shipping/v1/shipments';
  RESTRequest.Accept := 'application/json';

  AParameter := RESTRequest.Params.AddItem;
  AParameter.ContentType := ctAPPLICATION_JSON;
  AParameter.name := 'Content-Type';
  AParameter.Value := 'application/json';
  AParameter.Kind := TRESTRequestParameterKind.pkHTTPHEADER;

  RESTRequest.Params.AddHeader('account-number', ACCOUNT);

  try
    AJSONString := '{'+
                   '  "shipments": ['+
                   '    {'+
                   '      "shipment_reference": "139301",'+
                   '      "customer_reference_1": "1",'+
                   '      "from": {'+
                   '        "name": "MERCHANT_LOCATION",'+
                   '        "lines": ['+
                   '          "111 Bourke St"'+
                   '        ],'+
                   '        "suburb": "MELBOURNE",'+
                   '        "postcode": "3000",'+
                   '        "state": "VIC"'+
                   '      },'+
                   '      "to": {'+
                   '        "name": "STANDARD_ADDRESS",'+
                   '        "lines": ['+
                   '          "PO Box 123"'+
                   '        ],'+
                   '        "suburb": "Rye",'+
                   '        "state": "VIC",'+
                   '        "postcode": "3941",'+
                   '        "delivery_instructions": "please leave behind shed",'+
                   '        "email": "carl@gmai.co"'+
                   '      },'+
                   '      "items": ['+
                   '        {'+
                   '          "width": 10,'+
                   '          "height": 10,'+
                   '          "length": 10,'+
                   '          "weight": 0.19,'+
                   '          "product_id": "7E55",'+
                   '          "authority_to_leave": true'+
                   '        }'+
                   '      ]'+
                   '    }'+
                   '  ]'+
                   '}';

    RESTRequest.Body.Add(AJSONString, ctAPPLICATION_JSON);

    try
      RESTRequest.Execute;
    except on E: Exception do
      ;
    end;

    // If the StatusCode is 200, then the REST method worked

    AStatusCode := RESTRequest.Response.StatusCode;

    if AStatusCode <> 200 then
      AError := RESTRequest.Response.Content
    else
    begin
      AJSONValue := TJSonObject.ParseJSONValue(RESTRequest.Response.Content);

      AJSONValue.Free;

      AError := '';
    end;
  finally
    Result := (AError = '');
  end;
end;

When this is run, the status code is 404 and the content text is

{
  "code": "404",
  "name": "Not Found",
  "message": "Invalid HTTP method POST or request path /shipments. Please refer to the Developer Centre reference at https://developers.auspost.com.au/apis/shipping-and-tracking/reference for correct usage."
}

But if I setup that Create Shipment POST in Postman, with the Raw Body being the same JSON string, it works fine, and I get an appropriate response:

{
    "shipments": [
        {
            "shipment_id": "QdkK0EA99JoAAAFuba0gUzkF",
            "shipment_reference": "139301",
            "shipment_creation_date": "2019-11-05T11:45:03+11:00",
            "items": [
                {
                    "weight": 0.190,
                    "authority_to_leave": true,
                    "safe_drop_enabled": true,
                    "allow_partial_delivery": true,
                    "item_id": "kwgK0EA9Es8AAAFubq0gUzkF",
                    "tracking_details": {
                        "article_id": "111JD539319101000931501",
                        "consignment_id": "111JD5393191"
                    },
                    "product_id": "7E55",
                    "item_summary": {
                        "total_cost": 17.14,
                        "total_cost_ex_gst": 17.14,
                        "total_gst": 0.00,
                        "status": "Created"
                    },
                    "item_contents": []
                }
            ],
            "options": {},
            "shipment_summary": {
                "total_cost": 17.55,
                "total_cost_ex_gst": 15.95,
                "fuel_surcharge": 0.41,
                "total_gst": 1.60,
                "status": "Created",
                "tracking_summary": {
                    "Created": 1
                },
                "number_of_items": 1
            },
            "movement_type": "DESPATCH",
            "charge_to_account": "1001746337"
        }
    ]
}

What I don't understand is why Delphi is giving me this 404 error. If I don't include the body in the request when using Postman, it gives me this response:

{
    "errors": [
        {
            "code": "400",
            "name": "INVALID_PAYLOAD",
            "message": "Unable to deserialize payload.  Please ensure that the payload is valid JSON. Please note that when assigned, boolean fields can only accept 'true' or 'false' as values."
        }
    ]
}

But if I don't include the body in the request from Delphi, I get the same 404 error. Does anyone know of a different REST API that I can try my code with? Or has anyone successfully used the AusPost eParcel API from Delphi, even if it was done with Indy?

Found the problem, even if I can't explain it. Once I removed the Content-Type parameter code, the post method worked fine.

// AParameter := RESTRequest.Params.AddItem;
// AParameter.ContentType := ctAPPLICATION_JSON;
// AParameter.name := 'Content-Type';
// AParameter.Value := 'application/json';
// AParameter.Kind := TRESTRequestParameterKind.pkHTTPHEADER;

Somehow, including that in the request just stuffs things up. No explanation as to why, but it may just be one of those Delphi REST library quirks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM