Add customer communication system
This commit is contained in:
@@ -73,6 +73,21 @@ public static class ServiceCollectionExtensions
|
||||
return new RetryPolicyHandler(logger, options.MaxRetryAttempts);
|
||||
});
|
||||
|
||||
services.AddHttpClient<ICustomerService, CustomerService>((serviceProvider, client) =>
|
||||
{
|
||||
var options = serviceProvider.GetRequiredService<IOptions<LittleShopClientOptions>>().Value;
|
||||
client.BaseAddress = new Uri(options.BaseUrl);
|
||||
client.Timeout = TimeSpan.FromSeconds(options.TimeoutSeconds);
|
||||
client.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
})
|
||||
.AddHttpMessageHandler<ErrorHandlingMiddleware>()
|
||||
.AddHttpMessageHandler(serviceProvider =>
|
||||
{
|
||||
var logger = serviceProvider.GetRequiredService<ILogger<RetryPolicyHandler>>();
|
||||
var options = serviceProvider.GetRequiredService<IOptions<LittleShopClientOptions>>().Value;
|
||||
return new RetryPolicyHandler(logger, options.MaxRetryAttempts);
|
||||
});
|
||||
|
||||
// Register the main client
|
||||
services.AddScoped<ILittleShopClient, LittleShopClient>();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ public interface ILittleShopClient
|
||||
IAuthenticationService Authentication { get; }
|
||||
ICatalogService Catalog { get; }
|
||||
IOrderService Orders { get; }
|
||||
ICustomerService Customers { get; }
|
||||
}
|
||||
|
||||
public class LittleShopClient : ILittleShopClient
|
||||
@@ -16,14 +17,17 @@ public class LittleShopClient : ILittleShopClient
|
||||
public IAuthenticationService Authentication { get; }
|
||||
public ICatalogService Catalog { get; }
|
||||
public IOrderService Orders { get; }
|
||||
public ICustomerService Customers { get; }
|
||||
|
||||
public LittleShopClient(
|
||||
IAuthenticationService authenticationService,
|
||||
ICatalogService catalogService,
|
||||
IOrderService orderService)
|
||||
IOrderService orderService,
|
||||
ICustomerService customerService)
|
||||
{
|
||||
Authentication = authenticationService;
|
||||
Catalog = catalogService;
|
||||
Orders = orderService;
|
||||
Customers = customerService;
|
||||
}
|
||||
}
|
||||
@@ -37,4 +37,6 @@ public class PagedResult<T>
|
||||
public int PageNumber { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
public bool HasPreviousPage => PageNumber > 1;
|
||||
public bool HasNextPage => PageNumber < TotalPages;
|
||||
}
|
||||
68
LittleShop.Client/Models/Customer.cs
Normal file
68
LittleShop.Client/Models/Customer.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace LittleShop.Client.Models;
|
||||
|
||||
public class Customer
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public long TelegramUserId { get; set; }
|
||||
public string TelegramUsername { get; set; } = string.Empty;
|
||||
public string TelegramDisplayName { get; set; } = string.Empty;
|
||||
public string TelegramFirstName { get; set; } = string.Empty;
|
||||
public string TelegramLastName { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public bool AllowMarketing { get; set; }
|
||||
public bool AllowOrderUpdates { get; set; }
|
||||
public string Language { get; set; } = "en";
|
||||
public string Timezone { get; set; } = "UTC";
|
||||
public int TotalOrders { get; set; }
|
||||
public decimal TotalSpent { get; set; }
|
||||
public decimal AverageOrderValue { get; set; }
|
||||
public DateTime FirstOrderDate { get; set; }
|
||||
public DateTime LastOrderDate { get; set; }
|
||||
public string? CustomerNotes { get; set; }
|
||||
public bool IsBlocked { get; set; }
|
||||
public string? BlockReason { get; set; }
|
||||
public int RiskScore { get; set; }
|
||||
public int SuccessfulOrders { get; set; }
|
||||
public int CancelledOrders { get; set; }
|
||||
public int DisputedOrders { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public DateTime LastActiveAt { get; set; }
|
||||
public DateTime? DataRetentionDate { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
public string CustomerType { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class CreateCustomerRequest
|
||||
{
|
||||
public long TelegramUserId { get; set; }
|
||||
public string TelegramUsername { get; set; } = string.Empty;
|
||||
public string TelegramDisplayName { get; set; } = string.Empty;
|
||||
public string TelegramFirstName { get; set; } = string.Empty;
|
||||
public string TelegramLastName { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public bool AllowMarketing { get; set; } = false;
|
||||
public bool AllowOrderUpdates { get; set; } = true;
|
||||
public string Language { get; set; } = "en";
|
||||
public string Timezone { get; set; } = "UTC";
|
||||
}
|
||||
|
||||
public class UpdateCustomerRequest
|
||||
{
|
||||
public string TelegramUsername { get; set; } = string.Empty;
|
||||
public string TelegramDisplayName { get; set; } = string.Empty;
|
||||
public string TelegramFirstName { get; set; } = string.Empty;
|
||||
public string TelegramLastName { get; set; } = string.Empty;
|
||||
public string? Email { get; set; }
|
||||
public string? PhoneNumber { get; set; }
|
||||
public bool AllowMarketing { get; set; }
|
||||
public bool AllowOrderUpdates { get; set; }
|
||||
public string Language { get; set; } = "en";
|
||||
public string Timezone { get; set; } = "UTC";
|
||||
public string? CustomerNotes { get; set; }
|
||||
public bool IsBlocked { get; set; }
|
||||
public string? BlockReason { get; set; }
|
||||
}
|
||||
@@ -3,8 +3,9 @@ namespace LittleShop.Client.Models;
|
||||
public class Order
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string IdentityReference { get; set; } = string.Empty;
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public Guid? CustomerId { get; set; }
|
||||
public string? IdentityReference { get; set; }
|
||||
public int Status { get; set; }
|
||||
public decimal TotalAmount { get; set; }
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
public string ShippingName { get; set; } = string.Empty;
|
||||
@@ -34,7 +35,13 @@ public class OrderItem
|
||||
|
||||
public class CreateOrderRequest
|
||||
{
|
||||
public string IdentityReference { get; set; } = string.Empty;
|
||||
// Either Customer ID (for registered customers) OR Identity Reference (for anonymous)
|
||||
public Guid? CustomerId { get; set; }
|
||||
public string? IdentityReference { get; set; }
|
||||
|
||||
// Customer Information (collected at checkout for new customers)
|
||||
public CreateCustomerRequest? CustomerInfo { get; set; }
|
||||
|
||||
public string ShippingName { get; set; } = string.Empty;
|
||||
public string ShippingAddress { get; set; } = string.Empty;
|
||||
public string ShippingCity { get; set; } = string.Empty;
|
||||
|
||||
@@ -4,11 +4,11 @@ public class CryptoPayment
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid OrderId { get; set; }
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
public int Currency { get; set; }
|
||||
public string WalletAddress { get; set; } = string.Empty;
|
||||
public decimal RequiredAmount { get; set; }
|
||||
public decimal? PaidAmount { get; set; }
|
||||
public string Status { get; set; } = string.Empty;
|
||||
public int Status { get; set; }
|
||||
public string? TransactionHash { get; set; }
|
||||
public string? BTCPayInvoiceId { get; set; }
|
||||
public string? BTCPayCheckoutUrl { get; set; }
|
||||
@@ -19,5 +19,5 @@ public class CryptoPayment
|
||||
|
||||
public class CreatePaymentRequest
|
||||
{
|
||||
public string Currency { get; set; } = "BTC";
|
||||
public int Currency { get; set; } = 0; // BTC = 0
|
||||
}
|
||||
@@ -7,7 +7,7 @@ public class Product
|
||||
public string? Description { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
public decimal Weight { get; set; }
|
||||
public string WeightUnit { get; set; } = string.Empty;
|
||||
public int WeightUnit { get; set; }
|
||||
public Guid CategoryId { get; set; }
|
||||
public string? CategoryName { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
183
LittleShop.Client/Services/CustomerService.cs
Normal file
183
LittleShop.Client/Services/CustomerService.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LittleShop.Client.Models;
|
||||
|
||||
namespace LittleShop.Client.Services;
|
||||
|
||||
public class CustomerService : ICustomerService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ILogger<CustomerService> _logger;
|
||||
|
||||
public CustomerService(HttpClient httpClient, ILogger<CustomerService> logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<Customer>> GetCustomerByIdAsync(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"api/customers/{id}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var customer = await response.Content.ReadFromJsonAsync<Customer>();
|
||||
return ApiResponse<Customer>.Success(customer!);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<Customer>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get customer {CustomerId}", id);
|
||||
return ApiResponse<Customer>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<Customer>> GetCustomerByTelegramUserIdAsync(long telegramUserId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"api/customers/by-telegram/{telegramUserId}");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var customer = await response.Content.ReadFromJsonAsync<Customer>();
|
||||
return ApiResponse<Customer>.Success(customer!);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<Customer>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get customer by Telegram ID {TelegramUserId}", telegramUserId);
|
||||
return ApiResponse<Customer>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<Customer>> CreateCustomerAsync(CreateCustomerRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("api/customers", request);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var customer = await response.Content.ReadFromJsonAsync<Customer>();
|
||||
return ApiResponse<Customer>.Success(customer!);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<Customer>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to create customer");
|
||||
return ApiResponse<Customer>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<Customer>> GetOrCreateCustomerAsync(CreateCustomerRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync("api/customers/get-or-create", request);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var customer = await response.Content.ReadFromJsonAsync<Customer>();
|
||||
return ApiResponse<Customer>.Success(customer!);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<Customer>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get or create customer");
|
||||
return ApiResponse<Customer>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<Customer>> UpdateCustomerAsync(Guid id, UpdateCustomerRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PutAsJsonAsync($"api/customers/{id}", request);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var customer = await response.Content.ReadFromJsonAsync<Customer>();
|
||||
return ApiResponse<Customer>.Success(customer!);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<Customer>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to update customer {CustomerId}", id);
|
||||
return ApiResponse<Customer>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> BlockCustomerAsync(Guid id, string reason)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsJsonAsync($"api/customers/{id}/block", reason);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return ApiResponse<bool>.Success(true);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<bool>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to block customer {CustomerId}", id);
|
||||
return ApiResponse<bool>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> UnblockCustomerAsync(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _httpClient.PostAsync($"api/customers/{id}/unblock", null);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return ApiResponse<bool>.Success(true);
|
||||
}
|
||||
|
||||
var error = await response.Content.ReadAsStringAsync();
|
||||
return ApiResponse<bool>.Failure(error, response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to unblock customer {CustomerId}", id);
|
||||
return ApiResponse<bool>.Failure(
|
||||
ex.Message,
|
||||
System.Net.HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
LittleShop.Client/Services/ICustomerService.cs
Normal file
14
LittleShop.Client/Services/ICustomerService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using LittleShop.Client.Models;
|
||||
|
||||
namespace LittleShop.Client.Services;
|
||||
|
||||
public interface ICustomerService
|
||||
{
|
||||
Task<ApiResponse<Customer>> GetCustomerByIdAsync(Guid id);
|
||||
Task<ApiResponse<Customer>> GetCustomerByTelegramUserIdAsync(long telegramUserId);
|
||||
Task<ApiResponse<Customer>> CreateCustomerAsync(CreateCustomerRequest request);
|
||||
Task<ApiResponse<Customer>> GetOrCreateCustomerAsync(CreateCustomerRequest request);
|
||||
Task<ApiResponse<Customer>> UpdateCustomerAsync(Guid id, UpdateCustomerRequest request);
|
||||
Task<ApiResponse<bool>> BlockCustomerAsync(Guid id, string reason);
|
||||
Task<ApiResponse<bool>> UnblockCustomerAsync(Guid id);
|
||||
}
|
||||
@@ -7,6 +7,6 @@ public interface IOrderService
|
||||
Task<ApiResponse<Order>> CreateOrderAsync(CreateOrderRequest request);
|
||||
Task<ApiResponse<List<Order>>> GetOrdersByIdentityAsync(string identityReference);
|
||||
Task<ApiResponse<Order>> GetOrderByIdAsync(Guid id);
|
||||
Task<ApiResponse<CryptoPayment>> CreatePaymentAsync(Guid orderId, string currency);
|
||||
Task<ApiResponse<CryptoPayment>> CreatePaymentAsync(Guid orderId, int currency);
|
||||
Task<ApiResponse<List<CryptoPayment>>> GetOrderPaymentsAsync(Guid orderId);
|
||||
}
|
||||
@@ -90,7 +90,7 @@ public class OrderService : IOrderService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<CryptoPayment>> CreatePaymentAsync(Guid orderId, string currency)
|
||||
public async Task<ApiResponse<CryptoPayment>> CreatePaymentAsync(Guid orderId, int currency)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user