Etsy API updateInventory 响应 200 OK,但不更新库存数据

Etsy API updateInventory responds with 200 OK, but doesn't update inventory data

我有一个 Etsy 列表,其中有 1 个产品处于 draft 状态。当我尝试通过 updateInventory API 端点(文档:https://www.etsy.com/developers/documentation/reference/listing#method_updatelisting)更新此清单的清单时,出现错误。

我按照 'Updating a Listing with one product' 部分中的示例尝试更新价格、数量和 SKU,但没有成功:https://www.etsy.com/developers/documentation/getting_started/inventory

我收到 oauth signature_invalid 错误或 expected products property to be present 错误。

最后,我能够发出返回 200 OK 的请求。但列表中的数据没有更新。这是那个请求(为了便于阅读,我在 body 中添加了 new-lines,原始请求在 body 中没有空格或 new-lines):

POST https://openapi.etsy.com/v2/listings/808984070/inventory?method=PUT HTTP/1.1
Host: openapi.etsy.com
Authorization: OAuth oauth_consumer_key="xxxxxxxxx",oauth_nonce="xxxxxxxxx",oauth_signature="xxxxxxxxx",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1591886701",oauth_token="xxxxxxxxx",oauth_version="1.0"
Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
User-Agent: RestSharp/106.11.4.0
Connection: Keep-Alive
Request-Id: |38197ee2-416cf452906ba6a9.4.
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 192

{
  "products": [{
    "product_id": 4415387567,
    "sku": "TEST1",
    "property_values": [],
    "offerings":[{
      "offering_id": 4627746384,
      "price": "50.00",
      "quantity":100,
      "is_enabled":1,
      "is_deleted":0
    }],
    "is_deleted":0
  }]
}

为什么 Etsy 不更新库存,即使它 returns 200 OK?更具体地说,此请求中应更改什么以使 Etsy 更新库存数据?

P.S. 在达到上述要求之前我尝试了一些事情:

这个问题源于我对如何使用我正在使用的 C# 库 (RestSharp) 的误解。经过一些调整后,我能够生成一个有效的请求。这是它的样子:

PUT https://openapi.etsy.com/v2/listings/834145978/inventory HTTP/1.1
Host: openapi.etsy.com
Authorization: OAuth oauth_consumer_key="xxxxx",oauth_nonce="xxxxx",oauth_signature="xxxxx",oauth_signature_method="HMAC-SHA1",oauth_timestamp="xxxxx",oauth_token="xxxxx",oauth_version="1.0"
Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
User-Agent: RestSharp/106.11.4.0
Connection: Keep-Alive
Request-Id: |feb37503-4e0cc9e4a34b242a.4.
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 654

products=%5B%0D%0A%20%20%7B%0D%0A%20%20%20%20%22product_id%22%3A%204638831553%2C%0D%0A%20%20%20%20%22sku%22%3A%20%22N27%22%2C%0D%0A%20%20%20%20%22property_values%22%3A%20%5B%5D%2C%0D%0A%20%20%20%20%22offerings%22%3A%20%5B%0D%0A%20%20%20%20%20%20%7B%0D%0A%20%20%20%20%20%20%20%20%22offering_id%22%3A%204740581353%2C%0D%0A%20%20%20%20%20%20%20%20%22price%22%3A%20%2265.00%22%2C%0D%0A%20%20%20%20%20%20%20%20%22quantity%22%3A%201%2C%0D%0A%20%20%20%20%20%20%20%20%22is_enabled%22%3A%201%2C%0D%0A%20%20%20%20%20%20%20%20%22is_deleted%22%3A%200%0D%0A%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%5D%2C%0D%0A%20%20%20%20%22is_deleted%22%3A%200%0D%0A%20%20%7D%0D%0A%5D

需要注意的部分是:

  • 请求方式为PUT
  • Content-Type header 是 application/x-www-form-urlencoded
  • 这是请求 body 在成为请求 url-encoded 之前的样子:
products=[
  {
    "product_id": 4638831553,
    "sku": "N27",
    "property_values": [],
    "offerings": [
      {
        "offering_id": 4740581353,
        "price": "65.00",
        "quantity": 1,
        "is_enabled": 1,
        "is_deleted": 0
      }
    ],
    "is_deleted": 0
  }
]

不完全相关,但这是我使用 C# 和 RestSharp 在 Etsy 中创建新列表的代码。这是一个 work-in-progress 并且需要清理,但它有效:

public async Task<ListingModel> PostListing(ListingCreateModel createModel)
{
    // Create authenticator for the URL we will need to use.
    _restClient.Authenticator = MakeAuthenticatorForProtectedResource();

    // Update the create model, making sure we set the ShippingProfileId - this way
    // you won't need to set it manually in every listing afterwards.
    createModel.Quantity = createModel.Quantity;
    createModel.ShippingProfileId = ShippingProfileId;

    // Create a basic listing first, with minimal properties.
    var basicListing = ExecuteAndDeserialize<ListingListModel>(
        CreatePostRequest("listings", createModel)).Results.First();

    // Get the current products for this listing (we'll need to modify that object
    // and send it back to Etsy.)
    var productList = ExecuteAndDeserialize<dynamic>(
        CreateGetRequest($"listings/{basicListing.ListingId}/inventory"));
    
    // set SKU, price, and quantity (just experimenting, you can set any
    // supported properties you need here)
    productList.results.products[0].sku = createModel.Sku;
    productList.results.products[0].offerings[0].price =
        createModel.Price.ToString("0.00");
    productList.results.products[0].offerings[0].quantity =
        createModel.Quantity;

    // Now that we have an updated products list, call the PUT endpoint to
    // update the listing.
    // The CreatePutRequest method sets the 'Content-Type' header to
    // 'application/x-www-form-urlencoded'.
    // RestSharp will take care of serializing the `new { productList.results.products }`
    // object to the form-urlencoded format.
    var updatedListing = ExecuteAndDeserialize<dynamic>(
        await CreatePutRequest($"listings/{basicListing.ListingId}/inventory",
        new
        {
            productList.results.products
        }));
    
    var newListingList = ExecuteAndDeserialize<ListingListModel>(
        CreateGetRequest($"listings/{basicListing.ListingId}"));
    return newListingList.Results.First();
}

private async Task<IRestRequest> CreatePutRequest<TBody>(string resource, TBody body)
{
    var request = new RestRequest(resource)
    {
        Method = Method.PUT
    };

    request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
    request.AddObject(body);
    return request;
}

//-------- NOT SO RELEVANT TO THE QUESTION ----------

private IRestRequest CreateGetRequest(string resource)
{
    var request = new RestRequest(resource)
    {
        Method = Method.GET
    };

    return request;
}

private OAuth1Authenticator MakeAuthenticatorForProtectedResource()
{
    var auth = OAuth1Authenticator
        .ForProtectedResource(_authRepo.AuthInfo.AppKey, _authRepo.AuthInfo.SharedSecret,
        _authRepo.AuthInfo.AccessToken, _authRepo.AuthInfo.AccessTokenSecret);
    return auth;
}

private T ExecuteAndDeserialize<T>(IRestRequest req)
{
    var response = _restClient.Execute(req);
    if (response.IsSuccessful)
    {
        return JsonConvert.DeserializeObject<T>(response.Content);
    }

    if ((int) response.StatusCode >= 400 && (int) response.StatusCode < 500)
    {
        throw new ValidationException(response.Content);
    }
    throw new InvalidOperationException(response.Content);
}