diff --git a/EbayListingTool/Services/EbayListingService.cs b/EbayListingTool/Services/EbayListingService.cs
index fdd0f3d..746392e 100644
--- a/EbayListingTool/Services/EbayListingService.cs
+++ b/EbayListingTool/Services/EbayListingService.cs
@@ -1,4 +1,4 @@
-using System.Net.Http;
+using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using EbayListingTool.Models;
@@ -16,6 +16,13 @@ public class EbayListingService
private static readonly HttpClient _http = new(); // REST / Inventory / Account APIs
private static readonly HttpClient _photoHttp = new(); // Trading API (photo upload)
+ private static readonly JsonSerializerSettings _jsonSettings = new()
+ { NullValueHandling = NullValueHandling.Ignore };
+
+ private static readonly string _logPath = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "EbayListingTool", "crash_log.txt");
+
// Per-session cache of eBay account IDs — fetched once, reused for every listing
private string? _fulfillmentPolicyId;
private string? _paymentPolicyId;
@@ -43,13 +50,10 @@ public class EbayListingService
{
var token = await _auth.GetValidAccessTokenAsync();
- // 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);
- // 2. Resolve category if not set
if (string.IsNullOrEmpty(draft.CategoryId) && !string.IsNullOrEmpty(draft.CategoryName))
{
draft.CategoryId = await _categoryService.GetCategoryIdByKeywordAsync(draft.CategoryName)
@@ -59,14 +63,9 @@ public class EbayListingService
if (string.IsNullOrEmpty(draft.CategoryId))
throw new InvalidOperationException("Please select a category before posting.");
- // 3. Create inventory item
await CreateInventoryItemAsync(draft, imageUrls, token);
-
- // 4. Create offer
var offerId = await CreateOfferAsync(draft, token);
-
- // 5. Publish offer → get item ID
- var itemId = await PublishOfferAsync(offerId, token);
+ var itemId = await PublishOfferAsync(offerId, token);
draft.EbayItemId = itemId;
var domain = _auth.BaseUrl.Contains("sandbox") ? "sandbox.ebay.co.uk" : "ebay.co.uk";
@@ -77,11 +76,6 @@ public class EbayListingService
// ---- Setup: policies + location ----
- ///
- /// 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.
- ///
private async Task EnsurePoliciesAndLocationAsync(string token, string postcode)
{
var baseUrl = _auth.BaseUrl;
@@ -145,7 +139,7 @@ public class EbayListingService
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.");
+ "Please set one up in My eBay \u2192 Account \u2192 Business policies, then try again.");
}
if (_returnPolicyId == null)
@@ -167,7 +161,7 @@ public class EbayListingService
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.");
+ "Please set one up in My eBay \u2192 Account \u2192 Business policies, then try again.");
}
if (_merchantLocationKey == null)
@@ -179,13 +173,12 @@ public class EbayListingService
if (res.IsSuccessStatusCode)
{
- var arr = JObject.Parse(json)["locations"] as JArray;
+ 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);
@@ -194,73 +187,28 @@ public class EbayListingService
}
}
- /// Parses an eBay error JSON body into a user-friendly message.
- private static string ExtractEbayError(string json, string policyType)
- {
- try
- {
- var errors = JObject.Parse(json)["errors"] as JArray;
- var first = errors?.FirstOrDefault() as JObject;
- if (first != null)
- {
- var errorId = first["errorId"]?.Value() ?? 0;
- var longMsg = first["longMessage"]?.ToString() ?? first["message"]?.ToString() ?? "";
-
- // 20403 = account not opted in to Business Policies
- if (errorId == 20403 || longMsg.Contains("not eligible for Business Policy"))
- return $"Your eBay account is not set up for Business Policies, which are required to post listings via the API.\n\n" +
- $"To fix this:\n" +
- $"1. Log in to the eBay Seller Hub (or sandbox Seller Hub)\n" +
- $"2. Go to Account \u2192 Business policies\n" +
- $"3. Create at least one Postage, Payment and Returns policy\n\n" +
- $"Once done, click Post to eBay again.";
-
- if (!string.IsNullOrWhiteSpace(longMsg))
- return $"eBay {policyType} error: {longMsg}";
- }
- }
- catch { }
- return $"Could not fetch {policyType} from eBay. Please check your account settings.";
- }
-
- ///
- /// Called automatically on first 20403 error. Opts the account in to SELLING_POLICY_MANAGEMENT
- /// then creates minimal default policies so posting can proceed without manual eBay setup.
- ///
private static void LogSetup(string msg)
{
- var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "EbayListingTool");
- Directory.CreateDirectory(dir);
- File.AppendAllText(Path.Combine(dir, "crash_log.txt"),
- $"{DateTime.Now:HH:mm:ss} [Setup] {msg}{Environment.NewLine}");
+ Directory.CreateDirectory(Path.GetDirectoryName(_logPath)!);
+ File.AppendAllText(_logPath, $"{DateTime.Now:HH:mm:ss} [Setup] {msg}{Environment.NewLine}");
}
private async Task SetupDefaultBusinessPoliciesAsync(string token)
{
- // Step 1: opt in to the Business Policies programme
try
{
var optInBody = JsonConvert.SerializeObject(new { programType = "SELLING_POLICY_MANAGEMENT" });
using var req = MakeRequest(HttpMethod.Post,
$"{_auth.BaseUrl}/sell/account/v1/program/opt_in", token);
req.Content = new StringContent(optInBody, Encoding.UTF8, "application/json");
- var optInRes = await _http.SendAsync(req);
- var optInBody2 = await optInRes.Content.ReadAsStringAsync();
- LogSetup($"opt_in → {(int)optInRes.StatusCode} {optInBody2}");
+ var res = await _http.SendAsync(req);
+ var body = await res.Content.ReadAsStringAsync();
+ LogSetup($"opt_in \u2192 {(int)res.StatusCode} {body}");
}
catch (Exception ex) { LogSetup($"opt_in exception: {ex.Message}"); }
- // Step 2: create one policy of each required type (ignore errors — they may already exist)
- await TryCreateFulfillmentPolicyAsync(token);
- await TryCreatePaymentPolicyAsync(token);
- await TryCreateReturnPolicyAsync(token);
- }
-
- private async Task TryCreateFulfillmentPolicyAsync(string token)
- {
- try
- {
- var body = new
+ await Task.WhenAll(
+ TryCreatePolicyAsync(token, "fulfillment_policy", new
{
name = "Standard UK Shipping",
marketplaceId = "EBAY_GB",
@@ -278,52 +226,22 @@ public class EbayListingService
{
sortOrder = 1,
shippingCarrierCode = "ROYALMAIL",
- shippingServiceCode = "UK_OtherCourier",
+ shippingServiceCode = "UK_OtherCourier",
shippingCost = new { value = "2.85", currency = "GBP" },
additionalShippingCost = new { value = "0.00", currency = "GBP" }
}
}
}
}
- };
- using var req = MakeRequest(HttpMethod.Post,
- $"{_auth.BaseUrl}/sell/account/v1/fulfillment_policy", token);
- req.Content = new StringContent(
- JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
- var res2 = await _http.SendAsync(req);
- var body2 = await res2.Content.ReadAsStringAsync();
- LogSetup($"fulfillment_policy create → {(int)res2.StatusCode} {body2}");
- }
- catch (Exception ex) { LogSetup($"fulfillment_policy exception: {ex.Message}"); }
- }
-
- private async Task TryCreatePaymentPolicyAsync(string token)
- {
- try
- {
- var body = new
+ }),
+ TryCreatePolicyAsync(token, "payment_policy", new
{
- name = "Managed Payments",
- marketplaceId = "EBAY_GB",
- categoryTypes = new[] { new { name = "ALL_EXCLUDING_MOTORS_VEHICLES" } },
+ name = "Managed Payments",
+ marketplaceId = "EBAY_GB",
+ categoryTypes = new[] { new { name = "ALL_EXCLUDING_MOTORS_VEHICLES" } },
paymentMethods = Array.Empty