213 lines
10 KiB
C#
213 lines
10 KiB
C#
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Application.PlatformAdmin;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Application.Tenancy;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Growth;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.Platform;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.Infrastructure.PlatformAdmin;
|
|
|
|
internal sealed class PlatformPaymentSettingsService(ITenantExecutionScope tenantExecutionScope)
|
|
: IPlatformPaymentSettingsService
|
|
{
|
|
public Task<IReadOnlyCollection<PlatformPaymentApp>> GetAppsAsync(PlatformCapabilityActor actor, string? status,
|
|
int limit, CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync<IReadOnlyCollection<PlatformPaymentApp>>("platform payment apps list",
|
|
async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
var values = db.PlatformPaymentApps.AsNoTracking();
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
values = values.Where(value => value.Status == ParseEnum(status, PlatformPaymentAppStatus.Active));
|
|
return await values.OrderBy(value => value.AppCode).Take(Limit(limit)).ToArrayAsync(token);
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public Task<PlatformPaymentApp> UpsertAppAsync(PlatformCapabilityActor actor,
|
|
UpsertPlatformPaymentAppCommand command, CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync("platform payment app upsert", async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
var code = NormalizeCode(command.AppCode);
|
|
var item = command.Id.HasValue
|
|
? await db.PlatformPaymentApps.SingleOrDefaultAsync(value => value.Id == command.Id.Value, token)
|
|
: await db.PlatformPaymentApps.SingleOrDefaultAsync(value => value.AppCode == code, token);
|
|
if (item is null)
|
|
{
|
|
item = new PlatformPaymentApp { AppCode = code };
|
|
db.PlatformPaymentApps.Add(item);
|
|
}
|
|
|
|
item.AppName = command.AppName.Trim();
|
|
item.Status = command.Status;
|
|
item.SettlementMode = command.SettlementMode.Trim();
|
|
item.Description = Normalize(command.Description);
|
|
item.Metadata = command.Metadata;
|
|
item.UpdatedAt = DateTimeOffset.UtcNow;
|
|
AddPlatformAudit(db, actor, "platform.payment.app.upserted", "platform_payment_apps", item.Id,
|
|
new { item.AppCode, item.Status });
|
|
await db.SaveChangesAsync(token);
|
|
return item;
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyCollection<PlatformPaymentChannel>> GetChannelsAsync(PlatformCapabilityActor actor,
|
|
Guid? appId, string? status, int limit, CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync<IReadOnlyCollection<PlatformPaymentChannel>>("platform payment channels list",
|
|
async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
var values = db.PlatformPaymentChannels.AsNoTracking();
|
|
if (appId.HasValue) values = values.Where(value => value.AppId == appId);
|
|
if (!string.IsNullOrWhiteSpace(status))
|
|
values = values.Where(value =>
|
|
value.Status == ParseEnum(status, PlatformPaymentChannelStatus.Active));
|
|
return await values.OrderBy(value => value.Priority).ThenBy(value => value.Provider).Take(Limit(limit))
|
|
.ToArrayAsync(token);
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public Task<PlatformPaymentChannel> UpsertChannelAsync(PlatformCapabilityActor actor,
|
|
UpsertPlatformPaymentChannelCommand command, CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync("platform payment channel upsert", async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
if (!await db.PlatformPaymentApps.AnyAsync(value => value.Id == command.AppId, token))
|
|
throw Error("Platform payment app was not found.", "platform_payment_app_not_found");
|
|
var provider = NormalizeCode(command.Provider);
|
|
var item = command.Id.HasValue
|
|
? await db.PlatformPaymentChannels.SingleOrDefaultAsync(value => value.Id == command.Id.Value, token)
|
|
: await db.PlatformPaymentChannels.SingleOrDefaultAsync(
|
|
value => value.AppId == command.AppId && value.Provider == provider, token);
|
|
if (item is null)
|
|
{
|
|
item = new PlatformPaymentChannel { AppId = command.AppId, Provider = provider };
|
|
db.PlatformPaymentChannels.Add(item);
|
|
}
|
|
|
|
item.Mode = command.Mode.Trim();
|
|
item.Status = command.Status;
|
|
item.DisplayName = command.DisplayName.Trim();
|
|
item.SecretRef = Normalize(command.SecretRef);
|
|
item.CallbackPath = Normalize(command.CallbackPath);
|
|
item.Priority = command.Priority ?? item.Priority;
|
|
item.ConfigPublic = command.ConfigPublic;
|
|
item.Metadata = command.Metadata;
|
|
item.UpdatedAt = DateTimeOffset.UtcNow;
|
|
AddPlatformAudit(db, actor, "platform.payment.channel.upserted", "platform_payment_channels", item.Id,
|
|
new { item.Provider, item.Status, item.SecretRef });
|
|
await db.SaveChangesAsync(token);
|
|
return item;
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public Task<PlatformPaymentChannel> DisableChannelAsync(PlatformCapabilityActor actor, Guid channelId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync("platform payment channel disable", async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
var item = await db.PlatformPaymentChannels.SingleOrDefaultAsync(value => value.Id == channelId, token)
|
|
?? throw Error("Platform payment channel was not found.", "platform_payment_channel_not_found");
|
|
item.Status = PlatformPaymentChannelStatus.Disabled;
|
|
item.UpdatedAt = DateTimeOffset.UtcNow;
|
|
AddPlatformAudit(db, actor, "platform.payment.channel.disabled", "platform_payment_channels", item.Id,
|
|
new { item.Provider });
|
|
await db.SaveChangesAsync(token);
|
|
return item;
|
|
}, cancellationToken);
|
|
}
|
|
|
|
public Task<IReadOnlyCollection<PlatformBillingPaymentEvent>> GetEventsAsync(PlatformCapabilityActor actor,
|
|
string? status, int limit, CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync<IReadOnlyCollection<PlatformBillingPaymentEvent>>("platform payment events list",
|
|
async (services, token) =>
|
|
await services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>().PlatformBillingPaymentEvents.AsNoTracking()
|
|
.OrderByDescending(value => value.CreatedAt)
|
|
.Take(Limit(limit))
|
|
.ToArrayAsync(token), cancellationToken);
|
|
}
|
|
|
|
public Task<PlatformRebateSummary> GetRebateSummaryAsync(PlatformCapabilityActor actor,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return ExecuteAsync("platform rebate summary", async (services, token) =>
|
|
{
|
|
var db = services.GetRequiredService<IPlatformTenantCapabilitiesPersistence>();
|
|
var settlements = db.CommissionSettlements.AsNoTracking();
|
|
var gross = await settlements.SumAsync(value => (int?)value.GrossAmountCents, token) ?? 0;
|
|
var commission = await settlements.SumAsync(value => (int?)value.CommissionAmountCents, token) ?? 0;
|
|
var paid = await settlements.Where(value => value.Status == CommissionSettlementStatus.Paid)
|
|
.SumAsync(value => (int?)value.CommissionAmountCents, token) ?? 0;
|
|
var pending = await settlements
|
|
.Where(value =>
|
|
value.Status == CommissionSettlementStatus.Approved ||
|
|
value.Status == CommissionSettlementStatus.PendingReview)
|
|
.SumAsync(value => (int?)value.CommissionAmountCents, token) ?? 0;
|
|
var exceptions = await settlements.CountAsync(
|
|
value => value.Status == CommissionSettlementStatus.Rejected ||
|
|
value.Status == CommissionSettlementStatus.Cancelled, token);
|
|
return new PlatformRebateSummary(gross, commission, pending, paid, exceptions);
|
|
}, cancellationToken);
|
|
}
|
|
|
|
private Task<TResult> ExecuteAsync<TResult>(string reason,
|
|
Func<IServiceProvider, CancellationToken, Task<TResult>> operation, CancellationToken cancellationToken)
|
|
{
|
|
return tenantExecutionScope.ExecuteAsync(
|
|
new SystemScopeRequest(null, SystemScopeCallerType.Platform, nameof(PlatformPaymentSettingsService), reason,
|
|
Guid.NewGuid().ToString("N"), true), operation, cancellationToken);
|
|
}
|
|
|
|
private static PlatformCapabilityException Error(string message, string code)
|
|
{
|
|
return new PlatformCapabilityException(message, code);
|
|
}
|
|
|
|
private static int Limit(int value)
|
|
{
|
|
return Math.Clamp(value, 1, 500);
|
|
}
|
|
|
|
private static string NormalizeCode(string value)
|
|
{
|
|
return value.Trim().ToLowerInvariant().Replace("-", "_", StringComparison.Ordinal);
|
|
}
|
|
|
|
private static string? Normalize(string? value)
|
|
{
|
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
|
}
|
|
|
|
private static T ParseEnum<T>(string? value, T fallback) where T : struct, Enum
|
|
{
|
|
return Enum.TryParse<T>(value?.Trim().Replace("-", "_", StringComparison.Ordinal), true, out var parsed)
|
|
? parsed
|
|
: fallback;
|
|
}
|
|
|
|
private static void AddPlatformAudit(IPlatformTenantCapabilitiesPersistence db, PlatformCapabilityActor actor, string action,
|
|
string targetType, Guid targetId, object details)
|
|
{
|
|
db.AuditLogs.Add(new AuditLog
|
|
{
|
|
ActorUserId = actor.UserId,
|
|
Action = action,
|
|
TargetType = targetType,
|
|
TargetId = targetId.ToString(),
|
|
Details = JsonSerializer.SerializeToElement(details)
|
|
});
|
|
}
|
|
}
|