Fix all listing blockers: real policy IDs, merchant location, shared HttpClient
Business policies: EnsurePoliciesAndLocationAsync fetches fulfillment, payment and return policy IDs from the seller's eBay Account API on first post and caches them for the session. Uses the first policy of each type; gives a clear error pointing to My eBay → Business policies if none are configured. Merchant location: checks for existing locations via GET /sell/inventory/v1/location; if none found, creates a 'home' location using the seller's postcode. Location key is cached so the check only runs once. Cache cleared on disconnect so it works correctly after switching accounts. CreateOfferAsync now sends real fulfillmentPolicyId / paymentPolicyId / returnPolicyId instead of hardcoded policy name strings, and uses the resolved merchantLocationKey instead of the hardcoded "home" string. Removed BuildListingPolicies (inline shipping service codes no longer needed; shipping is governed by the fulfillment policy). Shared HttpClient: replaced BuildClient() (which returned a new HttpClient per call) with a static _http client and MakeRequest() helper that creates a pre-authorised HttpRequestMessage. UploadSinglePhotoAsync likewise uses a static _photoHttp client and HttpRequestMessage instead of new HttpClient() per photo. Removed placeholder ExternalPictureURL from Trading API SOAP body. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,17 +12,39 @@ public class EbayListingService
|
|||||||
private readonly EbayAuthService _auth;
|
private readonly EbayAuthService _auth;
|
||||||
private readonly EbayCategoryService _categoryService;
|
private readonly EbayCategoryService _categoryService;
|
||||||
|
|
||||||
|
// Shared clients — avoids socket exhaustion from per-call `new HttpClient()`
|
||||||
|
private static readonly HttpClient _http = new(); // REST / Inventory / Account APIs
|
||||||
|
private static readonly HttpClient _photoHttp = new(); // Trading API (photo upload)
|
||||||
|
|
||||||
|
// Per-session cache of eBay account IDs — fetched once, reused for every listing
|
||||||
|
private string? _fulfillmentPolicyId;
|
||||||
|
private string? _paymentPolicyId;
|
||||||
|
private string? _returnPolicyId;
|
||||||
|
private string? _merchantLocationKey;
|
||||||
|
|
||||||
public EbayListingService(EbayAuthService auth, EbayCategoryService categoryService)
|
public EbayListingService(EbayAuthService auth, EbayCategoryService categoryService)
|
||||||
{
|
{
|
||||||
_auth = auth;
|
_auth = auth;
|
||||||
_categoryService = categoryService;
|
_categoryService = categoryService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Call when the user disconnects so stale IDs are not reused after re-login.</summary>
|
||||||
|
public void ClearCache()
|
||||||
|
{
|
||||||
|
_fulfillmentPolicyId = null;
|
||||||
|
_paymentPolicyId = null;
|
||||||
|
_returnPolicyId = null;
|
||||||
|
_merchantLocationKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> PostListingAsync(ListingDraft draft)
|
public async Task<string> PostListingAsync(ListingDraft draft)
|
||||||
{
|
{
|
||||||
var token = await _auth.GetValidAccessTokenAsync();
|
var token = await _auth.GetValidAccessTokenAsync();
|
||||||
|
|
||||||
// 1. Upload photos and get URLs
|
// Resolve business policies and merchant location before touching inventory/offers
|
||||||
|
await EnsurePoliciesAndLocationAsync(token, draft.Postcode);
|
||||||
|
|
||||||
|
// 1. Upload photos and get eBay-hosted URLs
|
||||||
var imageUrls = await UploadPhotosAsync(draft.PhotoPaths, token);
|
var imageUrls = await UploadPhotosAsync(draft.PhotoPaths, token);
|
||||||
|
|
||||||
// 2. Resolve category if not set
|
// 2. Resolve category if not set
|
||||||
@@ -51,31 +73,156 @@ public class EbayListingService
|
|||||||
return draft.EbayListingUrl;
|
return draft.EbayListingUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Setup: policies + location ----
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches fulfillment, payment and return policy IDs from the seller's eBay account,
|
||||||
|
/// and ensures at least one merchant location exists (creating "home" from the seller's
|
||||||
|
/// postcode if needed). Results are cached for the session.
|
||||||
|
/// </summary>
|
||||||
|
private async Task EnsurePoliciesAndLocationAsync(string token, string postcode)
|
||||||
|
{
|
||||||
|
var baseUrl = _auth.BaseUrl;
|
||||||
|
|
||||||
|
if (_fulfillmentPolicyId == null)
|
||||||
|
{
|
||||||
|
using var req = MakeRequest(HttpMethod.Get,
|
||||||
|
$"{baseUrl}/sell/account/v1/fulfillment_policy?marketplace_id=EBAY_GB", token);
|
||||||
|
var res = await _http.SendAsync(req);
|
||||||
|
var json = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Could not fetch fulfillment policies ({(int)res.StatusCode}): {json}");
|
||||||
|
|
||||||
|
var arr = JObject.Parse(json)["fulfillmentPolicies"] as JArray;
|
||||||
|
_fulfillmentPolicyId = arr?.Count > 0
|
||||||
|
? arr[0]["fulfillmentPolicyId"]?.ToString()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (_fulfillmentPolicyId == null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"No fulfillment policy found on your eBay account.\n\n" +
|
||||||
|
"Please set one up in My eBay → Account → Business policies, then try again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_paymentPolicyId == null)
|
||||||
|
{
|
||||||
|
using var req = MakeRequest(HttpMethod.Get,
|
||||||
|
$"{baseUrl}/sell/account/v1/payment_policy?marketplace_id=EBAY_GB", token);
|
||||||
|
var res = await _http.SendAsync(req);
|
||||||
|
var json = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Could not fetch payment policies ({(int)res.StatusCode}): {json}");
|
||||||
|
|
||||||
|
var arr = JObject.Parse(json)["paymentPolicies"] as JArray;
|
||||||
|
_paymentPolicyId = arr?.Count > 0
|
||||||
|
? arr[0]["paymentPolicyId"]?.ToString()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (_paymentPolicyId == null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"No payment policy found on your eBay account.\n\n" +
|
||||||
|
"Please set one up in My eBay → Account → Business policies, then try again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_returnPolicyId == null)
|
||||||
|
{
|
||||||
|
using var req = MakeRequest(HttpMethod.Get,
|
||||||
|
$"{baseUrl}/sell/account/v1/return_policy?marketplace_id=EBAY_GB", token);
|
||||||
|
var res = await _http.SendAsync(req);
|
||||||
|
var json = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Could not fetch return policies ({(int)res.StatusCode}): {json}");
|
||||||
|
|
||||||
|
var arr = JObject.Parse(json)["returnPolicies"] as JArray;
|
||||||
|
_returnPolicyId = arr?.Count > 0
|
||||||
|
? arr[0]["returnPolicyId"]?.ToString()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (_returnPolicyId == null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"No return policy found on your eBay account.\n\n" +
|
||||||
|
"Please set one up in My eBay → Account → Business policies, then try again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_merchantLocationKey == null)
|
||||||
|
{
|
||||||
|
using var req = MakeRequest(HttpMethod.Get,
|
||||||
|
$"{baseUrl}/sell/inventory/v1/location", token);
|
||||||
|
var res = await _http.SendAsync(req);
|
||||||
|
var json = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (res.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var arr = JObject.Parse(json)["locations"] as JArray;
|
||||||
|
_merchantLocationKey = arr?.Count > 0
|
||||||
|
? arr[0]["merchantLocationKey"]?.ToString()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No existing locations — create one from the seller's postcode
|
||||||
|
if (_merchantLocationKey == null)
|
||||||
|
{
|
||||||
|
await CreateMerchantLocationAsync(token, postcode);
|
||||||
|
_merchantLocationKey = "home";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CreateMerchantLocationAsync(string token, string postcode)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(postcode))
|
||||||
|
postcode = "N/A"; // eBay allows this when postcode is genuinely unknown
|
||||||
|
|
||||||
|
var body = new
|
||||||
|
{
|
||||||
|
location = new
|
||||||
|
{
|
||||||
|
address = new { postalCode = postcode, country = "GB" }
|
||||||
|
},
|
||||||
|
locationTypes = new[] { "WAREHOUSE" },
|
||||||
|
name = "Home",
|
||||||
|
merchantLocationStatus = "ENABLED"
|
||||||
|
};
|
||||||
|
|
||||||
|
using var req = MakeRequest(HttpMethod.Post,
|
||||||
|
$"{_auth.BaseUrl}/sell/inventory/v1/location/home", token);
|
||||||
|
req.Content = new StringContent(
|
||||||
|
JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var res = await _http.SendAsync(req);
|
||||||
|
var json = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Could not create merchant location ({(int)res.StatusCode}): {json}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Inventory item ----
|
||||||
|
|
||||||
private async Task CreateInventoryItemAsync(ListingDraft draft, List<string> imageUrls, string token)
|
private async Task CreateInventoryItemAsync(ListingDraft draft, List<string> imageUrls, string token)
|
||||||
{
|
{
|
||||||
using var http = BuildClient(token);
|
|
||||||
|
|
||||||
var aspects = new Dictionary<string, List<string>>();
|
|
||||||
|
|
||||||
var inventoryItem = new
|
var inventoryItem = new
|
||||||
{
|
{
|
||||||
availability = new
|
availability = new
|
||||||
{
|
{
|
||||||
shipToLocationAvailability = new
|
shipToLocationAvailability = new { quantity = draft.Quantity }
|
||||||
{
|
|
||||||
quantity = draft.Quantity
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
condition = draft.ConditionId,
|
condition = draft.ConditionId,
|
||||||
conditionDescription = draft.Condition == ItemCondition.Used ? "Used - see photos" : null,
|
conditionDescription = draft.Condition == ItemCondition.Used ? "Used — see photos" : null,
|
||||||
description = draft.Description,
|
description = draft.Description,
|
||||||
title = draft.Title,
|
title = draft.Title,
|
||||||
product = new
|
product = new
|
||||||
{
|
{
|
||||||
title = draft.Title,
|
title = draft.Title,
|
||||||
description = draft.Description,
|
description = draft.Description,
|
||||||
imageUrls = imageUrls.Count > 0 ? imageUrls : null,
|
imageUrls = imageUrls.Count > 0 ? imageUrls : null,
|
||||||
aspects = aspects.Count > 0 ? aspects : null
|
aspects = (object?)null
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -85,40 +232,41 @@ public class EbayListingService
|
|||||||
});
|
});
|
||||||
|
|
||||||
var url = $"{_auth.BaseUrl}/sell/inventory/v1/inventory_item/{Uri.EscapeDataString(draft.Sku)}";
|
var url = $"{_auth.BaseUrl}/sell/inventory/v1/inventory_item/{Uri.EscapeDataString(draft.Sku)}";
|
||||||
var request = new HttpRequestMessage(HttpMethod.Put, url)
|
using var req = MakeRequest(HttpMethod.Put, url, token);
|
||||||
{
|
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
req.Content.Headers.Add("Content-Language", "en-GB");
|
||||||
};
|
|
||||||
request.Content.Headers.Add("Content-Language", "en-GB");
|
|
||||||
|
|
||||||
var response = await http.SendAsync(request);
|
var res = await _http.SendAsync(req);
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!res.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var error = await response.Content.ReadAsStringAsync();
|
var err = await res.Content.ReadAsStringAsync();
|
||||||
throw new HttpRequestException($"Failed to create inventory item: {error}");
|
throw new HttpRequestException($"Failed to create inventory item: {err}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Offer ----
|
||||||
|
|
||||||
private async Task<string> CreateOfferAsync(ListingDraft draft, string token)
|
private async Task<string> CreateOfferAsync(ListingDraft draft, string token)
|
||||||
{
|
{
|
||||||
using var http = BuildClient(token);
|
|
||||||
|
|
||||||
var listingPolicies = BuildListingPolicies(draft);
|
|
||||||
|
|
||||||
var offer = new
|
var offer = new
|
||||||
{
|
{
|
||||||
sku = draft.Sku,
|
sku = draft.Sku,
|
||||||
marketplaceId = "EBAY_GB",
|
marketplaceId = "EBAY_GB",
|
||||||
format = draft.Format == ListingFormat.Auction ? "AUCTION" : "FIXED_PRICE",
|
format = draft.Format == ListingFormat.Auction ? "AUCTION" : "FIXED_PRICE",
|
||||||
availableQuantity = draft.Quantity,
|
availableQuantity = draft.Quantity,
|
||||||
categoryId = draft.CategoryId,
|
categoryId = draft.CategoryId,
|
||||||
listingDescription = draft.Description,
|
listingDescription = draft.Description,
|
||||||
listingPolicies,
|
listingPolicies = new
|
||||||
|
{
|
||||||
|
fulfillmentPolicyId = _fulfillmentPolicyId,
|
||||||
|
paymentPolicyId = _paymentPolicyId,
|
||||||
|
returnPolicyId = _returnPolicyId
|
||||||
|
},
|
||||||
pricingSummary = new
|
pricingSummary = new
|
||||||
{
|
{
|
||||||
price = new { value = draft.Price.ToString("F2"), currency = "GBP" }
|
price = new { value = draft.Price.ToString("F2"), currency = "GBP" }
|
||||||
},
|
},
|
||||||
merchantLocationKey = "home",
|
merchantLocationKey = _merchantLocationKey,
|
||||||
tax = new { vatPercentage = 0, applyTax = false }
|
tax = new { vatPercentage = 0, applyTax = false }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -127,39 +275,45 @@ public class EbayListingService
|
|||||||
NullValueHandling = NullValueHandling.Ignore
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
});
|
});
|
||||||
|
|
||||||
var url = $"{_auth.BaseUrl}/sell/inventory/v1/offer";
|
using var req = MakeRequest(HttpMethod.Post,
|
||||||
var response = await http.PostAsync(url, new StringContent(json, Encoding.UTF8, "application/json"));
|
$"{_auth.BaseUrl}/sell/inventory/v1/offer", token);
|
||||||
var responseJson = await response.Content.ReadAsStringAsync();
|
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
var res = await _http.SendAsync(req);
|
||||||
|
var responseJson = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
throw new HttpRequestException($"Failed to create offer: {responseJson}");
|
throw new HttpRequestException($"Failed to create offer: {responseJson}");
|
||||||
|
|
||||||
var obj = JObject.Parse(responseJson);
|
return JObject.Parse(responseJson)["offerId"]?.ToString()
|
||||||
return obj["offerId"]?.ToString()
|
|
||||||
?? throw new InvalidOperationException("No offerId in create offer response.");
|
?? throw new InvalidOperationException("No offerId in create offer response.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Publish ----
|
||||||
|
|
||||||
private async Task<string> PublishOfferAsync(string offerId, string token)
|
private async Task<string> PublishOfferAsync(string offerId, string token)
|
||||||
{
|
{
|
||||||
using var http = BuildClient(token);
|
using var req = MakeRequest(HttpMethod.Post,
|
||||||
var url = $"{_auth.BaseUrl}/sell/inventory/v1/offer/{offerId}/publish";
|
$"{_auth.BaseUrl}/sell/inventory/v1/offer/{offerId}/publish", token);
|
||||||
var response = await http.PostAsync(url, new StringContent("{}", Encoding.UTF8, "application/json"));
|
req.Content = new StringContent("{}", Encoding.UTF8, "application/json");
|
||||||
var responseJson = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
var res = await _http.SendAsync(req);
|
||||||
|
var responseJson = await res.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!res.IsSuccessStatusCode)
|
||||||
throw new HttpRequestException($"Failed to publish offer: {responseJson}");
|
throw new HttpRequestException($"Failed to publish offer: {responseJson}");
|
||||||
|
|
||||||
var obj = JObject.Parse(responseJson);
|
return JObject.Parse(responseJson)["listingId"]?.ToString()
|
||||||
return obj["listingId"]?.ToString()
|
|
||||||
?? throw new InvalidOperationException("No listingId in publish response.");
|
?? throw new InvalidOperationException("No listingId in publish response.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Photo upload ----
|
||||||
|
|
||||||
private async Task<List<string>> UploadPhotosAsync(List<string> photoPaths, string token)
|
private async Task<List<string>> UploadPhotosAsync(List<string> photoPaths, string token)
|
||||||
{
|
{
|
||||||
var urls = new List<string>();
|
var urls = new List<string>();
|
||||||
if (photoPaths.Count == 0) return urls;
|
if (photoPaths.Count == 0) return urls;
|
||||||
|
|
||||||
// Use Trading API UploadSiteHostedPictures for each photo
|
|
||||||
var tradingBase = _auth.BaseUrl.Contains("sandbox")
|
var tradingBase = _auth.BaseUrl.Contains("sandbox")
|
||||||
? "https://api.sandbox.ebay.com/ws/api.dll"
|
? "https://api.sandbox.ebay.com/ws/api.dll"
|
||||||
: "https://api.ebay.com/ws/api.dll";
|
: "https://api.ebay.com/ws/api.dll";
|
||||||
@@ -167,7 +321,6 @@ public class EbayListingService
|
|||||||
foreach (var path in photoPaths.Take(12))
|
foreach (var path in photoPaths.Take(12))
|
||||||
{
|
{
|
||||||
if (!File.Exists(path)) continue;
|
if (!File.Exists(path)) continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = await UploadSinglePhotoAsync(path, tradingBase, token);
|
var url = await UploadSinglePhotoAsync(path, tradingBase, token);
|
||||||
@@ -176,7 +329,7 @@ public class EbayListingService
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Skip failed photo uploads, don't abort the whole listing
|
// Skip failed photos; don't abort the whole listing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +339,7 @@ public class EbayListingService
|
|||||||
private async Task<string?> UploadSinglePhotoAsync(string filePath, string tradingUrl, string token)
|
private async Task<string?> UploadSinglePhotoAsync(string filePath, string tradingUrl, string token)
|
||||||
{
|
{
|
||||||
var fileBytes = await File.ReadAllBytesAsync(filePath);
|
var fileBytes = await File.ReadAllBytesAsync(filePath);
|
||||||
var base64 = Convert.ToBase64String(fileBytes);
|
var ext = Path.GetExtension(filePath).TrimStart('.').ToLower();
|
||||||
var ext = Path.GetExtension(filePath).TrimStart('.').ToUpper();
|
|
||||||
|
|
||||||
var soapBody = $"""
|
var soapBody = $"""
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
@@ -197,78 +349,39 @@ public class EbayListingService
|
|||||||
</RequesterCredentials>
|
</RequesterCredentials>
|
||||||
<PictureName>{Path.GetFileNameWithoutExtension(filePath)}</PictureName>
|
<PictureName>{Path.GetFileNameWithoutExtension(filePath)}</PictureName>
|
||||||
<PictureSet>Supersize</PictureSet>
|
<PictureSet>Supersize</PictureSet>
|
||||||
<ExternalPictureURL>https://example.com/placeholder.jpg</ExternalPictureURL>
|
|
||||||
</UploadSiteHostedPicturesRequest>
|
</UploadSiteHostedPicturesRequest>
|
||||||
""";
|
""";
|
||||||
|
|
||||||
// For binary upload, use multipart
|
// Use HttpRequestMessage with _photoHttp so we don't create a new socket per photo
|
||||||
using var http = new HttpClient();
|
|
||||||
http.DefaultRequestHeaders.Add("X-EBAY-API-SITEID", "3");
|
|
||||||
http.DefaultRequestHeaders.Add("X-EBAY-API-COMPATIBILITY-LEVEL", "967");
|
|
||||||
http.DefaultRequestHeaders.Add("X-EBAY-API-CALL-NAME", "UploadSiteHostedPictures");
|
|
||||||
http.DefaultRequestHeaders.Add("X-EBAY-API-IAF-TOKEN", token);
|
|
||||||
|
|
||||||
using var content = new MultipartFormDataContent();
|
using var content = new MultipartFormDataContent();
|
||||||
content.Add(new StringContent(soapBody, Encoding.UTF8, "text/xml"), "XML Payload");
|
content.Add(new StringContent(soapBody, Encoding.UTF8, "text/xml"), "XML Payload");
|
||||||
var imageContent = new ByteArrayContent(fileBytes);
|
var imageContent = new ByteArrayContent(fileBytes);
|
||||||
imageContent.Headers.ContentType = new MediaTypeHeaderValue($"image/{ext.ToLower()}");
|
imageContent.Headers.ContentType = new MediaTypeHeaderValue($"image/{ext}");
|
||||||
content.Add(imageContent, "dummy", Path.GetFileName(filePath));
|
content.Add(imageContent, "image", Path.GetFileName(filePath));
|
||||||
|
|
||||||
var response = await http.PostAsync(tradingUrl, content);
|
using var req = new HttpRequestMessage(HttpMethod.Post, tradingUrl);
|
||||||
|
req.Headers.Add("X-EBAY-API-SITEID", "3"); // UK site
|
||||||
|
req.Headers.Add("X-EBAY-API-COMPATIBILITY-LEVEL", "967");
|
||||||
|
req.Headers.Add("X-EBAY-API-CALL-NAME", "UploadSiteHostedPictures");
|
||||||
|
req.Headers.Add("X-EBAY-API-IAF-TOKEN", token);
|
||||||
|
req.Content = content;
|
||||||
|
|
||||||
|
var response = await _photoHttp.SendAsync(req);
|
||||||
var responseXml = await response.Content.ReadAsStringAsync();
|
var responseXml = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
// Parse URL from XML response
|
|
||||||
var match = System.Text.RegularExpressions.Regex.Match(
|
var match = System.Text.RegularExpressions.Regex.Match(
|
||||||
responseXml, @"<FullURL>(.*?)</FullURL>");
|
responseXml, @"<FullURL>(.*?)</FullURL>");
|
||||||
return match.Success ? match.Groups[1].Value : null;
|
return match.Success ? match.Groups[1].Value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JObject BuildListingPolicies(ListingDraft draft)
|
// ---- Helpers ----
|
||||||
{
|
|
||||||
var (serviceCode, costValue) = draft.Postage switch
|
|
||||||
{
|
|
||||||
PostageOption.RoyalMailFirstClass => ("UK_RoyalMailFirstClass", "1.50"),
|
|
||||||
PostageOption.RoyalMailSecondClass => ("UK_RoyalMailSecondClass", "1.20"),
|
|
||||||
PostageOption.RoyalMailTracked24 => ("UK_RoyalMailTracked24", "2.95"),
|
|
||||||
PostageOption.RoyalMailTracked48 => ("UK_RoyalMailTracked48", "2.50"),
|
|
||||||
PostageOption.FreePostage => ("UK_RoyalMailSecondClass", "0.00"),
|
|
||||||
_ => ("UK_CollectionInPerson", "0.00")
|
|
||||||
};
|
|
||||||
|
|
||||||
return new JObject
|
/// <summary>Creates a pre-authorised request targeting the eBay REST APIs.</summary>
|
||||||
{
|
private HttpRequestMessage MakeRequest(HttpMethod method, string url, string token)
|
||||||
["shippingPolicyName"] = "Default",
|
|
||||||
["paymentPolicyName"] = "Default",
|
|
||||||
["returnPolicyName"] = "Default",
|
|
||||||
["shippingCostType"] = "FLAT_RATE",
|
|
||||||
["shippingOptions"] = new JArray
|
|
||||||
{
|
|
||||||
new JObject
|
|
||||||
{
|
|
||||||
["optionType"] = "DOMESTIC",
|
|
||||||
["costType"] = "FLAT_RATE",
|
|
||||||
["shippingServices"] = new JArray
|
|
||||||
{
|
|
||||||
new JObject
|
|
||||||
{
|
|
||||||
["shippingServiceCode"] = serviceCode,
|
|
||||||
["shippingCost"] = new JObject
|
|
||||||
{
|
|
||||||
["value"] = costValue,
|
|
||||||
["currency"] = "GBP"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClient BuildClient(string token)
|
|
||||||
{
|
{
|
||||||
var http = new HttpClient();
|
var req = new HttpRequestMessage(method, url);
|
||||||
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
http.DefaultRequestHeaders.Add("X-EBAY-C-MARKETPLACE-ID", "EBAY_GB");
|
req.Headers.Add("X-EBAY-C-MARKETPLACE-ID", "EBAY_GB");
|
||||||
return http;
|
return req;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ public partial class MainWindow : MetroWindow
|
|||||||
private void DisconnectBtn_Click(object sender, RoutedEventArgs e)
|
private void DisconnectBtn_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_auth.Disconnect();
|
_auth.Disconnect();
|
||||||
|
_listingService.ClearCache(); // clear cached policy/location IDs for next login
|
||||||
UpdateConnectionState();
|
UpdateConnectionState();
|
||||||
SetStatus("Disconnected from eBay.");
|
SetStatus("Disconnected from eBay.");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user