refactor(commerce): split administration services
This commit is contained in:
@@ -24,6 +24,7 @@ internal static class ApiPresentationExtensions
|
||||
services.AddProblemDetails();
|
||||
services.AddScoped<TenantAdminActorResolver>();
|
||||
services.AddScoped<DirectContentActorResolver>();
|
||||
services.AddScoped<CommerceAdminActorResolver>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class ActivationCodeAdministrationController(
|
||||
IActivationCodeAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpPost("code-batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建兑换码批次")]
|
||||
[ProducesResponseType<CodeBatchItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CodeBatchItem>> CreateCodeBatch(
|
||||
CreateCodeBatchDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.CreateCodeBatchAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("activation-codes")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询兑换码")]
|
||||
[ProducesResponseType<ActivationCodeList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ActivationCodeList>> ActivationCodes(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetActivationCodesAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("activation-codes/redeem")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("后台核销兑换码")]
|
||||
[ProducesResponseType<ActivationCodeItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ActivationCodeItem>> RedeemActivationCode(
|
||||
RedeemActivationCodeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.RedeemActivationCodeAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
115
Tiku.Api/Controllers/CommerceAdjustmentController.cs
Normal file
115
Tiku.Api/Controllers/CommerceAdjustmentController.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class CommerceAdjustmentController(
|
||||
ICommerceAdjustmentService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherList>> AdjustmentVouchers(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetAdjustmentVouchersAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/detail")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证详情")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> AdjustmentVoucherDetail(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetAdjustmentVoucherAsync(
|
||||
actorResolver.Resolve(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> CreateAdjustmentVoucher(
|
||||
CreateAdjustmentVoucherDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.CreateAdjustmentVoucherAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("审核或关闭调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> UpdateAdjustmentVoucherStatus(
|
||||
UpdateAdjustmentVoucherStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpdateAdjustmentVoucherStatusAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证事件")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherEventList>> AdjustmentVoucherEvents(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetAdjustmentVoucherEventsAsync(
|
||||
actorResolver.Resolve(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账统计报告")]
|
||||
[ProducesResponseType<TenantAdjustmentReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentReport>> AdjustmentVoucherReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetAdjustmentReportAsync(
|
||||
actorResolver.Resolve(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/anomalies")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账异常汇总")]
|
||||
[ProducesResponseType<TenantCommerceAnomalySummary>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCommerceAnomalySummary>> ReconciliationAnomalies(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetAnomalySummaryAsync(
|
||||
actorResolver.Resolve(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
28
Tiku.Api/Controllers/CommerceAdminActorResolver.cs
Normal file
28
Tiku.Api/Controllers/CommerceAdminActorResolver.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Security;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
public sealed class CommerceAdminActorResolver(
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant)
|
||||
{
|
||||
internal CommerceAdminActor Resolve()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
throw new CommerceException("Tenant commerce actor was not resolved.", "tenant_admin_access_denied");
|
||||
|
||||
return new CommerceAdminActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
|
||||
}
|
||||
|
||||
internal CommerceAdminQuery ToQuery(TenantCommerceQueryDto query)
|
||||
{
|
||||
return new CommerceAdminQuery(query.Provider, query.Status, query.Limit);
|
||||
}
|
||||
|
||||
internal TenantPointQuery ToPointQuery(TenantCommerceQueryDto query)
|
||||
{
|
||||
return new TenantPointQuery(query.Status, query.Limit, query.UserId, query.RegionId);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Tiku.Api.Controllers;
|
||||
[Route("api/student/commerce")]
|
||||
public sealed class CommerceController(
|
||||
ICommerceService commerceService,
|
||||
ICommerceAdminService commerceAdminService,
|
||||
IRefundAdministrationService refundAdministrationService,
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant,
|
||||
ITenantContextInitializer tenantInitializer,
|
||||
@@ -165,7 +165,7 @@ public sealed class CommerceController(
|
||||
RefundNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ProcessRefundNotificationAsync(
|
||||
return Ok(await refundAdministrationService.ProcessRefundNotificationAsync(
|
||||
await ResolveNotificationTenantAsync(tenantCode, cancellationToken),
|
||||
request.ToCommand(PaymentProviders.WechatPay),
|
||||
cancellationToken));
|
||||
@@ -180,7 +180,7 @@ public sealed class CommerceController(
|
||||
RefundNotificationDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ProcessRefundNotificationAsync(
|
||||
return Ok(await refundAdministrationService.ProcessRefundNotificationAsync(
|
||||
await ResolveNotificationTenantAsync(tenantCode, cancellationToken),
|
||||
request.ToCommand(PaymentProviders.Alipay),
|
||||
cancellationToken));
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class CommerceOrderAdministrationController(
|
||||
ICommerceOrderAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("orders")]
|
||||
[EndpointSummary("查询租户订单")]
|
||||
[ProducesResponseType<AdminOrderList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AdminOrderList>> Orders(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetOrdersAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("payments")]
|
||||
[EndpointSummary("查询租户支付记录")]
|
||||
[ProducesResponseType<AdminPaymentList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AdminPaymentList>> Payments(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPaymentsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
78
Tiku.Api/Controllers/CouponAdministrationController.cs
Normal file
78
Tiku.Api/Controllers/CouponAdministrationController.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class CouponAdministrationController(
|
||||
ICouponAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("coupons")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询租户优惠券")]
|
||||
[ProducesResponseType<TenantCouponList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponList>> Coupons(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetCouponsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("coupons")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新租户优惠券")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertCoupon(
|
||||
UpsertTenantCouponDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpsertCouponAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("coupons/redemptions")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询优惠券领取和核销记录")]
|
||||
[ProducesResponseType<TenantCouponRedemptionList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponRedemptionList>> CouponRedemptions(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetCouponRedemptionsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("coupons/report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询优惠券基础报表")]
|
||||
[ProducesResponseType<TenantCouponReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponReport>> CouponReport(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetCouponReportAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
64
Tiku.Api/Controllers/PaymentConfigurationController.cs
Normal file
64
Tiku.Api/Controllers/PaymentConfigurationController.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class PaymentConfigurationController(
|
||||
IPaymentConfigurationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("payment-accounts")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询租户支付账号")]
|
||||
[ProducesResponseType<IReadOnlyCollection<TenantPaymentProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IReadOnlyCollection<TenantPaymentProviderItem>>> PaymentAccounts(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPaymentAccountsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("payment-accounts")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新租户支付账号")]
|
||||
[ProducesResponseType<TenantPaymentProviderItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPaymentProviderItem>> UpsertPaymentAccount(
|
||||
UpsertPaymentAccountDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpsertPaymentAccountAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("secrets")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("写入或轮换租户密钥")]
|
||||
[ProducesResponseType<TenantSecretItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSecretItem>> UpsertSecret(
|
||||
UpsertTenantSecretDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpsertTenantSecretAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
120
Tiku.Api/Controllers/PointAdministrationController.cs
Normal file
120
Tiku.Api/Controllers/PointAdministrationController.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class PointAdministrationController(
|
||||
IPointAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("point-activity-tasks")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分活动任务")]
|
||||
[ProducesResponseType<TenantPointTaskList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointTaskList>> PointTasks(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPointTasksAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("point-activity-tasks")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新积分活动任务")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertPointTask(
|
||||
UpsertPointTaskDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpsertPointTaskAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-activity-claims")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分任务领取记录")]
|
||||
[ProducesResponseType<TenantPointClaimList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointClaimList>> PointClaims(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPointClaimsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-exchange-items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分兑换项")]
|
||||
[ProducesResponseType<TenantPointExchangeItemList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointExchangeItemList>> PointExchangeItems(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPointExchangeItemsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("point-exchange-items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新积分兑换项")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertPointExchangeItem(
|
||||
UpsertPointExchangeItemDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpsertPointExchangeItemAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-exchange-orders")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分兑换订单")]
|
||||
[ProducesResponseType<TenantPointExchangeOrderList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointExchangeOrderList>> PointExchangeOrders(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetPointExchangeOrdersAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("point-exchange-orders/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("更新积分兑换订单状态")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpdatePointExchangeOrderStatus(
|
||||
UpdatePointExchangeOrderStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpdatePointExchangeOrderStatusAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
162
Tiku.Api/Controllers/ReconciliationAdministrationController.cs
Normal file
162
Tiku.Api/Controllers/ReconciliationAdministrationController.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class ReconciliationAdministrationController(
|
||||
IReconciliationAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("reconciliation/batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账批次")]
|
||||
[ProducesResponseType<TenantReconciliationBatchList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationBatchList>> ReconciliationBatches(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetReconciliationBatchesAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建对账批次")]
|
||||
[ProducesResponseType<CommerceReconciliationBatch>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationBatch>> CreateReconciliationBatch(
|
||||
CreateReconciliationBatchDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.CreateReconciliationBatchAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/issues")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账异常")]
|
||||
[ProducesResponseType<TenantReconciliationIssueList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationIssueList>> ReconciliationIssues(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetReconciliationIssuesAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账明细")]
|
||||
[ProducesResponseType<TenantReconciliationItemList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationItemList>> ReconciliationItems(
|
||||
[FromQuery] Guid batchId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetReconciliationItemsAsync(
|
||||
actorResolver.Resolve(),
|
||||
batchId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/issues/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账工单事件")]
|
||||
[ProducesResponseType<TenantReconciliationIssueEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationIssueEventList>> ReconciliationIssueEvents(
|
||||
[FromQuery] Guid issueId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetReconciliationIssueEventsAsync(
|
||||
actorResolver.Resolve(),
|
||||
issueId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/issues/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("更新对账异常状态")]
|
||||
[ProducesResponseType<CommerceReconciliationIssue>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationIssue>> UpdateReconciliationIssue(
|
||||
UpdateReconciliationIssueDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpdateReconciliationIssueAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/preview")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("预览支付渠道账单对账")]
|
||||
[ProducesResponseType<ReconciliationImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReconciliationImportPreview>> PreviewReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.PreviewReconciliationImportAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToPreviewCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/import")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("导入支付渠道账单并创建对账批次")]
|
||||
[ProducesResponseType<CommerceReconciliationBatch>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationBatch>> ImportReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.ImportReconciliationAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToImportCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/provider-bills/jobs")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询渠道账单下载任务")]
|
||||
[ProducesResponseType<IReadOnlyCollection<BackgroundJobItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IReadOnlyCollection<BackgroundJobItem>>> ProviderBillJobs(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetProviderBillJobsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/provider-bills/request")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建渠道官方账单下载任务")]
|
||||
[ProducesResponseType<BackgroundJobItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<BackgroundJobItem>> RequestProviderBill(
|
||||
RequestProviderBillJobDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.RequestProviderBillJobAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
74
Tiku.Api/Controllers/RefundAdministrationController.cs
Normal file
74
Tiku.Api/Controllers/RefundAdministrationController.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class RefundAdministrationController(
|
||||
IRefundAdministrationService service,
|
||||
CommerceAdminActorResolver actorResolver) : ControllerBase
|
||||
{
|
||||
[HttpGet("refunds")]
|
||||
[EndpointSummary("查询退款申请")]
|
||||
[ProducesResponseType<TenantRefundList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantRefundList>> Refunds(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetRefundsAsync(
|
||||
actorResolver.Resolve(),
|
||||
actorResolver.ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("refunds")]
|
||||
[EndpointSummary("创建退款申请")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> CreateRefund(
|
||||
CreateRefundRequestDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.CreateRefundRequestAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("refunds/status")]
|
||||
[EndpointSummary("更新退款状态")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> UpdateRefundStatus(
|
||||
UpdateRefundStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.UpdateRefundStatusAsync(
|
||||
actorResolver.Resolve(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("refunds/{refundRequestId:guid}/events")]
|
||||
[EndpointSummary("查询退款事件")]
|
||||
[ProducesResponseType<TenantRefundEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantRefundEventList>> RefundEvents(
|
||||
Guid refundRequestId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await service.GetRefundEventsAsync(
|
||||
actorResolver.Resolve(),
|
||||
refundRequestId,
|
||||
cancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -1,589 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Api.Security;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Tags("租户端-交易运营")]
|
||||
[Authorize(Policy = BackendPermissions.TenantCommerceOperate)]
|
||||
[RequireSaasFeature(SaasFeatureCatalog.StudentStore)]
|
||||
[Produces("application/json")]
|
||||
[Route("api/tenant/commerce")]
|
||||
public sealed class TenantCommerceController(
|
||||
ICommerceAdminService commerceAdminService,
|
||||
ICurrentUser currentUser,
|
||||
ITenantContext currentTenant) : ControllerBase
|
||||
{
|
||||
[HttpGet("payment-accounts")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询租户支付账号")]
|
||||
[ProducesResponseType<IReadOnlyCollection<TenantPaymentProviderItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IReadOnlyCollection<TenantPaymentProviderItem>>> PaymentAccounts(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPaymentAccountsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("payment-accounts")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新租户支付账号")]
|
||||
[ProducesResponseType<TenantPaymentProviderItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPaymentProviderItem>> UpsertPaymentAccount(
|
||||
UpsertPaymentAccountDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpsertPaymentAccountAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("secrets")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("写入或轮换租户密钥")]
|
||||
[ProducesResponseType<TenantSecretItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantSecretItem>> UpsertSecret(
|
||||
UpsertTenantSecretDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpsertTenantSecretAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("orders")]
|
||||
[EndpointSummary("查询租户订单")]
|
||||
[ProducesResponseType<AdminOrderList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AdminOrderList>> Orders(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetOrdersAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("payments")]
|
||||
[EndpointSummary("查询租户支付记录")]
|
||||
[ProducesResponseType<AdminPaymentList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<AdminPaymentList>> Payments(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPaymentsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("code-batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建兑换码批次")]
|
||||
[ProducesResponseType<CodeBatchItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CodeBatchItem>> CreateCodeBatch(
|
||||
CreateCodeBatchDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.CreateCodeBatchAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("activation-codes")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询兑换码")]
|
||||
[ProducesResponseType<ActivationCodeList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ActivationCodeList>> ActivationCodes(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetActivationCodesAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("activation-codes/redeem")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("后台核销兑换码")]
|
||||
[ProducesResponseType<ActivationCodeItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ActivationCodeItem>> RedeemActivationCode(
|
||||
RedeemActivationCodeDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.RedeemActivationCodeAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-activity-tasks")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分活动任务")]
|
||||
[ProducesResponseType<TenantPointTaskList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointTaskList>> PointTasks(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPointTasksAsync(
|
||||
ResolveActor(),
|
||||
ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("point-activity-tasks")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新积分活动任务")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertPointTask(
|
||||
UpsertPointTaskDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpsertPointTaskAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-activity-claims")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分任务领取记录")]
|
||||
[ProducesResponseType<TenantPointClaimList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointClaimList>> PointClaims(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPointClaimsAsync(
|
||||
ResolveActor(),
|
||||
ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-exchange-items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分兑换项")]
|
||||
[ProducesResponseType<TenantPointExchangeItemList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointExchangeItemList>> PointExchangeItems(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPointExchangeItemsAsync(
|
||||
ResolveActor(),
|
||||
ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("point-exchange-items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新积分兑换项")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertPointExchangeItem(
|
||||
UpsertPointExchangeItemDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpsertPointExchangeItemAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("point-exchange-orders")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询积分兑换订单")]
|
||||
[ProducesResponseType<TenantPointExchangeOrderList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantPointExchangeOrderList>> PointExchangeOrders(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetPointExchangeOrdersAsync(
|
||||
ResolveActor(),
|
||||
ToPointQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("point-exchange-orders/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("更新积分兑换订单状态")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpdatePointExchangeOrderStatus(
|
||||
UpdatePointExchangeOrderStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpdatePointExchangeOrderStatusAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("coupons")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询租户优惠券")]
|
||||
[ProducesResponseType<TenantCouponList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponList>> Coupons(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetCouponsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPut("coupons")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("新增或更新租户优惠券")]
|
||||
[ProducesResponseType<object>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<object>> UpsertCoupon(
|
||||
UpsertTenantCouponDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpsertCouponAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("coupons/redemptions")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询优惠券领取和核销记录")]
|
||||
[ProducesResponseType<TenantCouponRedemptionList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponRedemptionList>> CouponRedemptions(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetCouponRedemptionsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("coupons/report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询优惠券基础报表")]
|
||||
[ProducesResponseType<TenantCouponReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCouponReport>> CouponReport(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetCouponReportAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("refunds")]
|
||||
[EndpointSummary("查询退款申请")]
|
||||
[ProducesResponseType<TenantRefundList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantRefundList>> Refunds(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetRefundsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("refunds")]
|
||||
[EndpointSummary("创建退款申请")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> CreateRefund(
|
||||
CreateRefundRequestDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.CreateRefundRequestAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("refunds/status")]
|
||||
[EndpointSummary("更新退款状态")]
|
||||
[ProducesResponseType<CommerceRefundRequest>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceRefundRequest>> UpdateRefundStatus(
|
||||
UpdateRefundStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpdateRefundStatusAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("refunds/{refundRequestId:guid}/events")]
|
||||
[EndpointSummary("查询退款事件")]
|
||||
[ProducesResponseType<TenantRefundEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantRefundEventList>> RefundEvents(
|
||||
Guid refundRequestId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetRefundEventsAsync(
|
||||
ResolveActor(),
|
||||
refundRequestId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账批次")]
|
||||
[ProducesResponseType<TenantReconciliationBatchList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationBatchList>> ReconciliationBatches(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationBatchesAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/batches")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建对账批次")]
|
||||
[ProducesResponseType<CommerceReconciliationBatch>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationBatch>> CreateReconciliationBatch(
|
||||
CreateReconciliationBatchDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.CreateReconciliationBatchAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/issues")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账异常")]
|
||||
[ProducesResponseType<TenantReconciliationIssueList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationIssueList>> ReconciliationIssues(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationIssuesAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/items")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账明细")]
|
||||
[ProducesResponseType<TenantReconciliationItemList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationItemList>> ReconciliationItems(
|
||||
[FromQuery] Guid batchId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationItemsAsync(
|
||||
ResolveActor(),
|
||||
batchId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/issues/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账工单事件")]
|
||||
[ProducesResponseType<TenantReconciliationIssueEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantReconciliationIssueEventList>> ReconciliationIssueEvents(
|
||||
[FromQuery] Guid issueId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetReconciliationIssueEventsAsync(
|
||||
ResolveActor(),
|
||||
issueId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/issues/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("更新对账异常状态")]
|
||||
[ProducesResponseType<CommerceReconciliationIssue>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationIssue>> UpdateReconciliationIssue(
|
||||
UpdateReconciliationIssueDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpdateReconciliationIssueAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherList>> AdjustmentVouchers(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVouchersAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/detail")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证详情")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> AdjustmentVoucherDetail(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVoucherAsync(
|
||||
ResolveActor(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> CreateAdjustmentVoucher(
|
||||
CreateAdjustmentVoucherDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.CreateAdjustmentVoucherAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("adjustment-vouchers/status")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("审核或关闭调账凭证")]
|
||||
[ProducesResponseType<CommerceAdjustmentVoucher>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceAdjustmentVoucher>> UpdateAdjustmentVoucherStatus(
|
||||
UpdateAdjustmentVoucherStatusDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.UpdateAdjustmentVoucherStatusAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/events")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账凭证事件")]
|
||||
[ProducesResponseType<TenantAdjustmentVoucherEventList>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentVoucherEventList>> AdjustmentVoucherEvents(
|
||||
[FromQuery] Guid voucherId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentVoucherEventsAsync(
|
||||
ResolveActor(),
|
||||
voucherId,
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("adjustment-vouchers/report")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询调账统计报告")]
|
||||
[ProducesResponseType<TenantAdjustmentReport>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantAdjustmentReport>> AdjustmentVoucherReport(CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAdjustmentReportAsync(
|
||||
ResolveActor(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/anomalies")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询对账异常汇总")]
|
||||
[ProducesResponseType<TenantCommerceAnomalySummary>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TenantCommerceAnomalySummary>> ReconciliationAnomalies(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetAnomalySummaryAsync(
|
||||
ResolveActor(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/preview")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("预览支付渠道账单对账")]
|
||||
[ProducesResponseType<ReconciliationImportPreview>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ReconciliationImportPreview>> PreviewReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.PreviewReconciliationImportAsync(
|
||||
ResolveActor(),
|
||||
request.ToPreviewCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/import")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("导入支付渠道账单并创建对账批次")]
|
||||
[ProducesResponseType<CommerceReconciliationBatch>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CommerceReconciliationBatch>> ImportReconciliation(
|
||||
PreviewReconciliationImportDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.ImportReconciliationAsync(
|
||||
ResolveActor(),
|
||||
request.ToImportCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpGet("reconciliation/provider-bills/jobs")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("查询渠道账单下载任务")]
|
||||
[ProducesResponseType<IReadOnlyCollection<BackgroundJobItem>>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IReadOnlyCollection<BackgroundJobItem>>> ProviderBillJobs(
|
||||
[FromQuery] TenantCommerceQueryDto query,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.GetProviderBillJobsAsync(
|
||||
ResolveActor(),
|
||||
ToQuery(query),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
[HttpPost("reconciliation/provider-bills/request")]
|
||||
[Authorize(Policy = TikuPolicies.TenantCommerceOperateAllScope)]
|
||||
[EndpointSummary("创建渠道官方账单下载任务")]
|
||||
[ProducesResponseType<BackgroundJobItem>(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<BackgroundJobItem>> RequestProviderBill(
|
||||
RequestProviderBillJobDto request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return Ok(await commerceAdminService.RequestProviderBillJobAsync(
|
||||
ResolveActor(),
|
||||
request.ToCommand(),
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
private CommerceAdminActor ResolveActor()
|
||||
{
|
||||
if (currentTenant.TenantId is null || currentUser.UserId is null)
|
||||
throw new CommerceException("Tenant commerce actor was not resolved.", "tenant_admin_access_denied");
|
||||
|
||||
return new CommerceAdminActor(currentTenant.TenantId.Value, currentUser.UserId.Value);
|
||||
}
|
||||
|
||||
private static CommerceAdminQuery ToQuery(TenantCommerceQueryDto query)
|
||||
{
|
||||
return new CommerceAdminQuery(query.Provider, query.Status, query.Limit);
|
||||
}
|
||||
|
||||
private static TenantPointQuery ToPointQuery(TenantCommerceQueryDto query)
|
||||
{
|
||||
return new TenantPointQuery(query.Status, query.Limit, query.UserId, query.RegionId);
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,7 @@ public sealed record UpdateReconciliationIssueCommand(
|
||||
string? Note,
|
||||
Guid? AssignedTo = null);
|
||||
|
||||
public interface ICommerceAdminService
|
||||
public interface IPaymentConfigurationService
|
||||
{
|
||||
Task<IReadOnlyCollection<TenantPaymentProviderItem>> GetPaymentAccountsAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -296,7 +296,10 @@ public interface ICommerceAdminService
|
||||
CommerceAdminActor actor,
|
||||
UpsertTenantSecretCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ICommerceOrderAdministrationService
|
||||
{
|
||||
Task<AdminOrderList> GetOrdersAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
@@ -306,7 +309,10 @@ public interface ICommerceAdminService
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IActivationCodeAdministrationService
|
||||
{
|
||||
Task<CodeBatchItem> CreateCodeBatchAsync(
|
||||
CommerceAdminActor actor,
|
||||
CreateCodeBatchCommand command,
|
||||
@@ -321,42 +327,10 @@ public interface ICommerceAdminService
|
||||
CommerceAdminActor actor,
|
||||
RedeemActivationCodeCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
Task<TenantPointTaskList> GetPointTasksAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointActivityTask> UpsertPointTaskAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpsertPointTaskCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointClaimList> GetPointClaimsAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointExchangeItemList> GetPointExchangeItemsAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointExchangeItem> UpsertPointExchangeItemAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpsertPointExchangeItemCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointExchangeOrderList> GetPointExchangeOrdersAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointExchangeOrder> UpdatePointExchangeOrderStatusAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpdatePointExchangeOrderStatusCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
public interface ICouponAdministrationService
|
||||
{
|
||||
Task<TenantCouponList> GetCouponsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
@@ -376,7 +350,10 @@ public interface ICommerceAdminService
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IRefundAdministrationService
|
||||
{
|
||||
Task<TenantRefundList> GetRefundsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
@@ -397,6 +374,14 @@ public interface ICommerceAdminService
|
||||
Guid refundRequestId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CommerceRefundRequest> ProcessRefundNotificationAsync(
|
||||
Guid tenantId,
|
||||
RefundNotificationCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IReconciliationAdministrationService
|
||||
{
|
||||
Task<TenantReconciliationBatchList> GetReconciliationBatchesAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
@@ -427,10 +412,29 @@ public interface ICommerceAdminService
|
||||
Guid issueId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantCommerceAnomalySummary> GetAnomalySummaryAsync(
|
||||
Task<ReconciliationImportPreview> PreviewReconciliationImportAsync(
|
||||
CommerceAdminActor actor,
|
||||
PreviewReconciliationImportCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CommerceReconciliationBatch> ImportReconciliationAsync(
|
||||
CommerceAdminActor actor,
|
||||
ImportReconciliationCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<BackgroundJobItem> RequestProviderBillJobAsync(
|
||||
CommerceAdminActor actor,
|
||||
RequestProviderBillJobCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyCollection<BackgroundJobItem>> GetProviderBillJobsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ICommerceAdjustmentService
|
||||
{
|
||||
Task<TenantAdjustmentVoucherList> GetAdjustmentVouchersAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
@@ -460,28 +464,7 @@ public interface ICommerceAdminService
|
||||
CommerceAdminActor actor,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<ReconciliationImportPreview> PreviewReconciliationImportAsync(
|
||||
Task<TenantCommerceAnomalySummary> GetAnomalySummaryAsync(
|
||||
CommerceAdminActor actor,
|
||||
PreviewReconciliationImportCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CommerceReconciliationBatch> ImportReconciliationAsync(
|
||||
CommerceAdminActor actor,
|
||||
ImportReconciliationCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<BackgroundJobItem> RequestProviderBillJobAsync(
|
||||
CommerceAdminActor actor,
|
||||
RequestProviderBillJobCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IReadOnlyCollection<BackgroundJobItem>> GetProviderBillJobsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<CommerceRefundRequest> ProcessRefundNotificationAsync(
|
||||
Guid tenantId,
|
||||
RefundNotificationCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
42
Tiku.Application/Points/IPointAdministrationService.cs
Normal file
42
Tiku.Application/Points/IPointAdministrationService.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Application.Points;
|
||||
|
||||
public interface IPointAdministrationService
|
||||
{
|
||||
Task<TenantPointTaskList> GetPointTasksAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointActivityTask> UpsertPointTaskAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpsertPointTaskCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointClaimList> GetPointClaimsAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointExchangeItemList> GetPointExchangeItemsAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointExchangeItem> UpsertPointExchangeItemAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpsertPointExchangeItemCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<TenantPointExchangeOrderList> GetPointExchangeOrdersAsync(
|
||||
CommerceAdminActor actor,
|
||||
TenantPointQuery query,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PointExchangeOrder> UpdatePointExchangeOrderStatusAsync(
|
||||
CommerceAdminActor actor,
|
||||
UpdatePointExchangeOrderStatusCommand command,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -6,7 +6,8 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class ActivationCodeAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IActivationCodeAdministrationService
|
||||
{
|
||||
public async Task<CodeBatchItem> CreateCodeBatchAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -6,7 +6,8 @@ using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class CommerceAdjustmentService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), ICommerceAdjustmentService
|
||||
{
|
||||
public async Task<TenantAdjustmentVoucherList> GetAdjustmentVouchersAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -182,46 +183,7 @@ internal sealed partial class CommerceAdminService
|
||||
.SumAsync(item => item.AmountCents, cancellationToken));
|
||||
}
|
||||
|
||||
public async Task<TenantReconciliationItemList> GetReconciliationItemsAsync(
|
||||
CommerceAdminActor actor,
|
||||
Guid batchId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var batchExists = await dbContext.CommerceReconciliationBatches.AnyAsync(
|
||||
item => item.TenantId == actor.TenantId && item.Id == batchId,
|
||||
cancellationToken);
|
||||
if (!batchExists)
|
||||
throw new CommerceException("Reconciliation batch was not found.", "reconciliation_batch_not_found");
|
||||
|
||||
var items = await dbContext.CommerceReconciliationItems.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.BatchId == batchId)
|
||||
.OrderBy(item => item.RowNo)
|
||||
.Take(500)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new TenantReconciliationItemList(items);
|
||||
}
|
||||
|
||||
public async Task<TenantReconciliationIssueEventList> GetReconciliationIssueEventsAsync(
|
||||
CommerceAdminActor actor,
|
||||
Guid issueId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var issueExists = await dbContext.CommerceReconciliationIssues.AnyAsync(
|
||||
item => item.TenantId == actor.TenantId && item.Id == issueId,
|
||||
cancellationToken);
|
||||
if (!issueExists)
|
||||
throw new CommerceException("Reconciliation issue was not found.", "reconciliation_issue_not_found");
|
||||
|
||||
var events = await dbContext.CommerceReconciliationIssueEvents.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.IssueId == issueId)
|
||||
.OrderBy(item => item.CreatedAt)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new TenantReconciliationIssueEventList(events);
|
||||
}
|
||||
|
||||
public async Task<TenantCommerceAnomalySummary> GetAnomalySummaryAsync(
|
||||
public async Task<TenantCommerceAnomalySummary> GetAnomalySummaryAsync(
|
||||
CommerceAdminActor actor,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -256,187 +218,4 @@ internal sealed partial class CommerceAdminService
|
||||
pendingPayments,
|
||||
mismatchCount);
|
||||
}
|
||||
|
||||
public async Task<ReconciliationImportPreview> PreviewReconciliationImportAsync(
|
||||
CommerceAdminActor actor,
|
||||
PreviewReconciliationImportCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
return BuildImportPreview(command.Provider, command.Rows);
|
||||
}
|
||||
|
||||
public async Task<CommerceReconciliationBatch> ImportReconciliationAsync(
|
||||
CommerceAdminActor actor,
|
||||
ImportReconciliationCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var preview = BuildImportPreview(command.Provider, command.Rows);
|
||||
var batch = new CommerceReconciliationBatch
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
CreatedBy = actor.UserId,
|
||||
Provider = NormalizeProvider(command.Provider),
|
||||
BillDate = command.BillDate,
|
||||
BillType = command.BillType,
|
||||
Source = ReconciliationSource.ManualUpload,
|
||||
SourceName = command.SourceName.Trim(),
|
||||
SourceHash = preview.SourceHash,
|
||||
Status = preview.InvalidCount == 0
|
||||
? ReconciliationBatchStatus.Completed
|
||||
: ReconciliationBatchStatus.CompletedWithIssues,
|
||||
TotalCount = preview.TotalCount,
|
||||
MatchedCount = preview.TotalCount - preview.InvalidCount,
|
||||
MismatchCount = preview.InvalidCount,
|
||||
AmountCents = preview.AmountCents,
|
||||
RefundAmountCents = preview.RefundAmountCents,
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Metadata = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
preview.PaymentCount,
|
||||
preview.RefundCount,
|
||||
preview.InvalidCount
|
||||
})
|
||||
};
|
||||
dbContext.CommerceReconciliationBatches.Add(batch);
|
||||
var rowNo = 0;
|
||||
foreach (var row in EnumerateImportRows(command.Rows))
|
||||
{
|
||||
rowNo++;
|
||||
var item = CreateReconciliationItem(actor.TenantId, batch.Id, rowNo, NormalizeProvider(command.Provider),
|
||||
row);
|
||||
dbContext.CommerceReconciliationItems.Add(item);
|
||||
if (item.MatchStatus != ReconciliationMatchStatus.Matched)
|
||||
dbContext.CommerceReconciliationIssues.Add(new CommerceReconciliationIssue
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
BatchId = batch.Id,
|
||||
Provider = item.Provider,
|
||||
TransactionType = item.TransactionType,
|
||||
IssueNo = $"RC{DateTimeOffset.UtcNow:yyyyMMddHHmmss}{rowNo:0000}",
|
||||
MatchStatus = item.MatchStatus switch
|
||||
{
|
||||
ReconciliationMatchStatus.MissingLocal => ReconciliationIssueMatchStatus.MissingLocal,
|
||||
ReconciliationMatchStatus.MissingProvider => ReconciliationIssueMatchStatus.MissingProvider,
|
||||
ReconciliationMatchStatus.Duplicate => ReconciliationIssueMatchStatus.Duplicate,
|
||||
ReconciliationMatchStatus.StatusMismatch => ReconciliationIssueMatchStatus.StatusMismatch,
|
||||
_ => ReconciliationIssueMatchStatus.AmountMismatch
|
||||
},
|
||||
Severity = item.Severity,
|
||||
Status = ReconciliationIssueStatus.Open,
|
||||
OrderNo = item.OrderNo,
|
||||
RefundNo = item.RefundNo,
|
||||
ProviderTradeNo = item.ProviderTradeNo,
|
||||
ProviderRefundNo = item.ProviderRefundNo,
|
||||
AmountCents = item.AmountCents,
|
||||
RefundAmountCents = item.RefundAmountCents,
|
||||
Summary = item.IssueCode,
|
||||
CreatedBy = actor.UserId,
|
||||
Metadata = item.Details
|
||||
});
|
||||
}
|
||||
|
||||
await AddAuditAsync(actor, "commerce.reconciliation.imported", "commerce_reconciliation_batches", batch.Id,
|
||||
new { batch.Provider, batch.BillDate, batch.TotalCount }, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return batch;
|
||||
}
|
||||
|
||||
public async Task<BackgroundJobItem> RequestProviderBillJobAsync(
|
||||
CommerceAdminActor actor,
|
||||
RequestProviderBillJobCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var job = await backgroundJobQueue.EnqueueAsync(
|
||||
new CreateBackgroundJobCommand(
|
||||
actor.TenantId,
|
||||
"commerce_reconciliation",
|
||||
JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
provider = NormalizeProvider(command.Provider),
|
||||
command.BillDate,
|
||||
billType = command.BillType.ToString()
|
||||
}),
|
||||
command.RunAfter,
|
||||
5),
|
||||
cancellationToken);
|
||||
await AddAuditAsync(actor, "commerce.reconciliation.provider_bill_requested", "background_jobs", job.Id,
|
||||
new { command.Provider, command.BillDate, command.BillType }, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return job;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<BackgroundJobItem>> GetProviderBillJobsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
return await backgroundJobOperations.ListAsync(actor.TenantId, "commerce_reconciliation",
|
||||
Math.Clamp(query.Limit ?? 50, 1, 200), cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<CommerceRefundRequest> ProcessRefundNotificationAsync(
|
||||
Guid tenantId,
|
||||
RefundNotificationCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var provider = NormalizeProvider(command.Provider);
|
||||
var refund = await dbContext.CommerceRefundRequests.SingleOrDefaultAsync(
|
||||
item => item.TenantId == tenantId && item.RefundNo == command.RefundNo,
|
||||
cancellationToken) ?? throw new CommerceException("Refund request was not found.", "refund_not_found");
|
||||
var eventId = string.IsNullOrWhiteSpace(command.EventId)
|
||||
? $"{provider}:{command.RefundNo}:{command.Status}"
|
||||
: command.EventId.Trim();
|
||||
var duplicate = await dbContext.PaymentEvents.AnyAsync(
|
||||
item => item.TenantId == tenantId &&
|
||||
item.Provider == provider &&
|
||||
item.EventType == "refund" &&
|
||||
item.EventId == eventId,
|
||||
cancellationToken);
|
||||
if (duplicate) return refund;
|
||||
|
||||
dbContext.PaymentEvents.Add(new PaymentEvent
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Provider = provider,
|
||||
EventType = "refund",
|
||||
EventId = eventId,
|
||||
SignatureValid = true,
|
||||
Payload = JsonObjectOrDefault(command.Payload),
|
||||
ProcessedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
var fromStatus = refund.Status;
|
||||
if (fromStatus != command.Status && IsAllowedRefundTransition(fromStatus, command.Status))
|
||||
{
|
||||
refund.Status = command.Status;
|
||||
refund.ProviderRefundNo = string.IsNullOrWhiteSpace(command.ProviderRefundNo)
|
||||
? refund.ProviderRefundNo
|
||||
: command.ProviderRefundNo.Trim();
|
||||
if (command.Status == CommerceRefundStatus.Succeeded)
|
||||
{
|
||||
refund.SucceededAt = DateTimeOffset.UtcNow;
|
||||
await ApplyRefundToOrderAsync(refund, cancellationToken);
|
||||
}
|
||||
else if (command.Status == CommerceRefundStatus.Failed)
|
||||
{
|
||||
refund.FailedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
dbContext.CommerceRefundEvents.Add(new CommerceRefundEvent
|
||||
{
|
||||
TenantId = tenantId,
|
||||
RefundRequestId = refund.Id,
|
||||
FromStatus = fromStatus,
|
||||
ToStatus = command.Status,
|
||||
EventType = "provider_notify",
|
||||
Details = JsonObjectOrDefault(command.Payload)
|
||||
});
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return refund;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService(
|
||||
TikuDbContext dbContext,
|
||||
ITenantSecretProtector tenantSecretProtector,
|
||||
ITenantExternalProviderConfigService providerConfigService,
|
||||
ICurrentAccessContext currentAccessContext,
|
||||
IBackgroundJobQueue backgroundJobQueue,
|
||||
IBackgroundJobOperations backgroundJobOperations) : ICommerceAdminService
|
||||
{
|
||||
}
|
||||
@@ -4,7 +4,8 @@ using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class CouponAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), ICouponAdministrationService
|
||||
{
|
||||
public async Task<TenantCouponList> GetCouponsAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -13,9 +13,9 @@ using NotificationSeverity = Tiku.Domain.Commerce.NotificationSeverity;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal abstract partial class CommerceAdministrationServiceBase
|
||||
{
|
||||
private async Task AssertAdminAsync(CommerceAdminActor actor, CancellationToken cancellationToken)
|
||||
protected async Task AssertAdminAsync(CommerceAdminActor actor, CancellationToken cancellationToken)
|
||||
{
|
||||
var access = await currentAccessContext.GetAsync(cancellationToken);
|
||||
if (!access.IsCurrentTenantMember ||
|
||||
@@ -25,7 +25,7 @@ internal sealed partial class CommerceAdminService
|
||||
throw new CommerceException("Tenant admin access is required.", "tenant_admin_access_denied");
|
||||
}
|
||||
|
||||
private async Task<CurrentDataScope> RequireDataScopeAsync(
|
||||
protected async Task<CurrentDataScope> RequireDataScopeAsync(
|
||||
CommerceAdminActor actor,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -33,7 +33,7 @@ internal sealed partial class CommerceAdminService
|
||||
return (await currentAccessContext.GetAsync(cancellationToken)).DataScope;
|
||||
}
|
||||
|
||||
private static TenantPaymentProviderItem ToPaymentAccountItem(TenantExternalProviderItem item)
|
||||
protected static TenantPaymentProviderItem ToPaymentAccountItem(TenantExternalProviderItem item)
|
||||
{
|
||||
return new TenantPaymentProviderItem(
|
||||
item.Id,
|
||||
@@ -48,28 +48,28 @@ internal sealed partial class CommerceAdminService
|
||||
item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static TenantSecretItem ToSecretItem(TenantSecret item)
|
||||
protected static TenantSecretItem ToSecretItem(TenantSecret item)
|
||||
{
|
||||
return new TenantSecretItem(item.Id, item.Purpose, item.Provider, item.SecretKey, item.SecretRef,
|
||||
item.Status.ToString(),
|
||||
item.RotatedAt, item.ExpiresAt, item.UpdatedAt);
|
||||
}
|
||||
|
||||
private static CodeBatchItem ToCodeBatchItem(CodeBatch item)
|
||||
protected static CodeBatchItem ToCodeBatchItem(CodeBatch item)
|
||||
{
|
||||
return new CodeBatchItem(item.Id, item.Name, item.TotalCount, item.Days ?? 0, item.RegionId, item.SaleType,
|
||||
item.Channel,
|
||||
item.DefaultUnitPriceCents, item.CostPriceCents, item.IssuedAt, item.Remark, item.CreatedAt);
|
||||
}
|
||||
|
||||
private static ActivationCodeItem ToActivationCodeItem(ActivationCode item)
|
||||
protected static ActivationCodeItem ToActivationCodeItem(ActivationCode item)
|
||||
{
|
||||
return new ActivationCodeItem(item.Id, item.BatchId, item.Code, item.Days, item.IsUsed, item.UsedBy,
|
||||
item.UsedAt, item.SaleType,
|
||||
item.SoldTo, item.Remark, item.CreatedAt);
|
||||
}
|
||||
|
||||
private static CommerceOrderItem ToOrderItem(Order order)
|
||||
protected static CommerceOrderItem ToOrderItem(Order order)
|
||||
{
|
||||
return new CommerceOrderItem(order.Id, order.OrderNo, order.Status.ToString(), order.PlanId, order.RegionId,
|
||||
order.ProductType,
|
||||
@@ -77,7 +77,7 @@ internal sealed partial class CommerceAdminService
|
||||
order.TradeNo, order.Days, order.PaidAt, order.CreatedAt, order.RawPayload);
|
||||
}
|
||||
|
||||
private static CommercePaymentItem ToPaymentItem(Payment payment, string orderNo)
|
||||
protected static CommercePaymentItem ToPaymentItem(Payment payment, string orderNo)
|
||||
{
|
||||
return new CommercePaymentItem(payment.Id, payment.OrderId, orderNo, payment.Provider, payment.Method,
|
||||
payment.Status.ToString(),
|
||||
@@ -85,28 +85,28 @@ internal sealed partial class CommerceAdminService
|
||||
JsonSerializer.SerializeToElement(new { }), payment.RawPayload);
|
||||
}
|
||||
|
||||
private static OrderStatus ParseOrderStatus(string? status)
|
||||
protected static OrderStatus ParseOrderStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<OrderStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Order status is invalid.", "invalid_order_status");
|
||||
}
|
||||
|
||||
private static PaymentStatus ParsePaymentStatus(string? status)
|
||||
protected static PaymentStatus ParsePaymentStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<PaymentStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Payment status is invalid.", "invalid_payment_status");
|
||||
}
|
||||
|
||||
private static PointActivityTaskStatus ParsePointTaskStatus(string? status)
|
||||
protected static PointActivityTaskStatus ParsePointTaskStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<PointActivityTaskStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Point task status is invalid.", "invalid_point_task_status");
|
||||
}
|
||||
|
||||
private static PointExchangeItemStatus ParsePointExchangeItemStatus(string? status)
|
||||
protected static PointExchangeItemStatus ParsePointExchangeItemStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<PointExchangeItemStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
@@ -114,7 +114,7 @@ internal sealed partial class CommerceAdminService
|
||||
"invalid_point_exchange_item_status");
|
||||
}
|
||||
|
||||
private static PointExchangeOrderStatus ParsePointExchangeOrderStatus(string? status)
|
||||
protected static PointExchangeOrderStatus ParsePointExchangeOrderStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<PointExchangeOrderStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
@@ -122,21 +122,21 @@ internal sealed partial class CommerceAdminService
|
||||
"invalid_point_exchange_order_status");
|
||||
}
|
||||
|
||||
private static CouponRedemptionStatus ParseCouponRedemptionStatus(string? status)
|
||||
protected static CouponRedemptionStatus ParseCouponRedemptionStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<CouponRedemptionStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Coupon redemption status is invalid.", "invalid_coupon_redemption_status");
|
||||
}
|
||||
|
||||
private static CommerceRefundStatus ParseRefundStatus(string? status)
|
||||
protected static CommerceRefundStatus ParseRefundStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<CommerceRefundStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Refund status is invalid.", "invalid_refund_status");
|
||||
}
|
||||
|
||||
private static ReconciliationBatchStatus ParseReconciliationBatchStatus(string? status)
|
||||
protected static ReconciliationBatchStatus ParseReconciliationBatchStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<ReconciliationBatchStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
@@ -144,7 +144,7 @@ internal sealed partial class CommerceAdminService
|
||||
"invalid_reconciliation_batch_status");
|
||||
}
|
||||
|
||||
private static ReconciliationIssueStatus ParseReconciliationIssueStatus(string? status)
|
||||
protected static ReconciliationIssueStatus ParseReconciliationIssueStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<ReconciliationIssueStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
@@ -152,14 +152,14 @@ internal sealed partial class CommerceAdminService
|
||||
"invalid_reconciliation_issue_status");
|
||||
}
|
||||
|
||||
private static CommerceAdjustmentVoucherStatus ParseAdjustmentVoucherStatus(string? status)
|
||||
protected static CommerceAdjustmentVoucherStatus ParseAdjustmentVoucherStatus(string? status)
|
||||
{
|
||||
return Enum.TryParse<CommerceAdjustmentVoucherStatus>(NormalizeEnum(status), true, out var parsed)
|
||||
? parsed
|
||||
: throw new CommerceException("Adjustment voucher status is invalid.", "invalid_adjustment_voucher_status");
|
||||
}
|
||||
|
||||
private async Task AssertOptionalReferenceAsync<TEntity>(
|
||||
protected async Task AssertOptionalReferenceAsync<TEntity>(
|
||||
DbSet<TEntity> set,
|
||||
Guid tenantId,
|
||||
Guid? id,
|
||||
@@ -175,7 +175,7 @@ internal sealed partial class CommerceAdminService
|
||||
if (!exists) throw new CommerceException("Referenced commerce entity was not found.", code);
|
||||
}
|
||||
|
||||
private async Task ApplyRefundToOrderAsync(CommerceRefundRequest refund, CancellationToken cancellationToken)
|
||||
protected async Task ApplyRefundToOrderAsync(CommerceRefundRequest refund, CancellationToken cancellationToken)
|
||||
{
|
||||
var order = await dbContext.Orders.SingleAsync(
|
||||
item => item.TenantId == refund.TenantId && item.Id == refund.OrderId,
|
||||
@@ -204,7 +204,7 @@ internal sealed partial class CommerceAdminService
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAllowedRefundTransition(CommerceRefundStatus from, CommerceRefundStatus to)
|
||||
protected static bool IsAllowedRefundTransition(CommerceRefundStatus from, CommerceRefundStatus to)
|
||||
{
|
||||
return from switch
|
||||
{
|
||||
@@ -217,7 +217,7 @@ internal sealed partial class CommerceAdminService
|
||||
};
|
||||
}
|
||||
|
||||
private static bool IsAllowedAdjustmentTransition(CommerceAdjustmentVoucherStatus from,
|
||||
protected static bool IsAllowedAdjustmentTransition(CommerceAdjustmentVoucherStatus from,
|
||||
CommerceAdjustmentVoucherStatus to)
|
||||
{
|
||||
return from switch
|
||||
@@ -231,7 +231,7 @@ internal sealed partial class CommerceAdminService
|
||||
};
|
||||
}
|
||||
|
||||
private void AddRefundEvent(
|
||||
protected void AddRefundEvent(
|
||||
CommerceRefundRequest refund,
|
||||
CommerceRefundStatus? fromStatus,
|
||||
CommerceRefundStatus toStatus,
|
||||
@@ -251,7 +251,7 @@ internal sealed partial class CommerceAdminService
|
||||
});
|
||||
}
|
||||
|
||||
private Task AddAuditAsync(
|
||||
protected Task AddAuditAsync(
|
||||
CommerceAdminActor actor,
|
||||
string action,
|
||||
string targetType,
|
||||
@@ -272,12 +272,12 @@ internal sealed partial class CommerceAdminService
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static string NormalizeEnum(string? value)
|
||||
protected static string NormalizeEnum(string? value)
|
||||
{
|
||||
return string.Concat((value ?? string.Empty).Split(['_', '-', ' '], StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private static string NormalizeProvider(string? provider)
|
||||
protected static string NormalizeProvider(string? provider)
|
||||
{
|
||||
var normalized = (provider ?? string.Empty).Trim().ToLowerInvariant()
|
||||
.Replace("-", "_", StringComparison.Ordinal);
|
||||
@@ -290,7 +290,7 @@ internal sealed partial class CommerceAdminService
|
||||
};
|
||||
}
|
||||
|
||||
private static JsonElement WithPaymentMode(JsonElement element, string? mode)
|
||||
protected static JsonElement WithPaymentMode(JsonElement element, string? mode)
|
||||
{
|
||||
var values = new Dictionary<string, JsonElement>(StringComparer.Ordinal);
|
||||
if (element.ValueKind == JsonValueKind.Object)
|
||||
@@ -302,7 +302,7 @@ internal sealed partial class CommerceAdminService
|
||||
return JsonSerializer.SerializeToElement(values);
|
||||
}
|
||||
|
||||
private static string? GetJsonString(JsonElement element, params string[] keys)
|
||||
protected static string? GetJsonString(JsonElement element, params string[] keys)
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object) return null;
|
||||
|
||||
@@ -313,14 +313,14 @@ internal sealed partial class CommerceAdminService
|
||||
return null;
|
||||
}
|
||||
|
||||
private static JsonElement JsonObjectOrDefault(JsonElement element)
|
||||
protected static JsonElement JsonObjectOrDefault(JsonElement element)
|
||||
{
|
||||
return element.ValueKind == JsonValueKind.Object
|
||||
? element.Clone()
|
||||
: JsonSerializer.SerializeToElement(new { });
|
||||
}
|
||||
|
||||
private static void AssertNoSecrets(JsonElement element, string path)
|
||||
protected static void AssertNoSecrets(JsonElement element, string path)
|
||||
{
|
||||
if (element.ValueKind != JsonValueKind.Object) return;
|
||||
|
||||
@@ -338,7 +338,7 @@ internal sealed partial class CommerceAdminService
|
||||
}
|
||||
}
|
||||
|
||||
private static ReconciliationImportPreview BuildImportPreview(string provider, JsonElement rows)
|
||||
protected static ReconciliationImportPreview BuildImportPreview(string provider, JsonElement rows)
|
||||
{
|
||||
var normalizedProvider = NormalizeProvider(provider);
|
||||
var parsedRows = EnumerateImportRows(rows).ToArray();
|
||||
@@ -360,7 +360,7 @@ internal sealed partial class CommerceAdminService
|
||||
sourceHash);
|
||||
}
|
||||
|
||||
private static IEnumerable<ReconciliationImportRow> EnumerateImportRows(JsonElement rows)
|
||||
protected static IEnumerable<ReconciliationImportRow> EnumerateImportRows(JsonElement rows)
|
||||
{
|
||||
if (rows.ValueKind != JsonValueKind.Array)
|
||||
throw new CommerceException("Reconciliation rows must be an array.", "invalid_reconciliation_rows");
|
||||
@@ -417,7 +417,7 @@ internal sealed partial class CommerceAdminService
|
||||
}
|
||||
}
|
||||
|
||||
private static ReconciliationImportRow InvalidImportRow(string issueCode, JsonElement row)
|
||||
protected static ReconciliationImportRow InvalidImportRow(string issueCode, JsonElement row)
|
||||
{
|
||||
return new ReconciliationImportRow(
|
||||
ReconciliationTransactionType.Payment,
|
||||
@@ -434,7 +434,7 @@ internal sealed partial class CommerceAdminService
|
||||
row.Clone());
|
||||
}
|
||||
|
||||
private static CommerceReconciliationItem CreateReconciliationItem(
|
||||
protected static CommerceReconciliationItem CreateReconciliationItem(
|
||||
Guid tenantId,
|
||||
Guid batchId,
|
||||
int rowNo,
|
||||
@@ -465,7 +465,7 @@ internal sealed partial class CommerceAdminService
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetJsonInt(JsonElement element, params string[] keys)
|
||||
protected static int GetJsonInt(JsonElement element, params string[] keys)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
@@ -480,19 +480,19 @@ internal sealed partial class CommerceAdminService
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static string GenerateActivationCode()
|
||||
protected static string GenerateActivationCode()
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[8];
|
||||
RandomNumberGenerator.Fill(bytes);
|
||||
return $"TKU{Convert.ToHexString(bytes)}";
|
||||
}
|
||||
|
||||
private static string FormatCny(int cents)
|
||||
protected static string FormatCny(int cents)
|
||||
{
|
||||
return (cents / 100m).ToString("0.00", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private sealed record ReconciliationImportRow(
|
||||
protected sealed record ReconciliationImportRow(
|
||||
ReconciliationTransactionType TransactionType,
|
||||
string? ProviderTradeNo,
|
||||
string? ProviderRefundNo,
|
||||
@@ -0,0 +1,25 @@
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Application.Security;
|
||||
using Tiku.Application.Tenancy;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed record CommerceAdministrationDependencies(
|
||||
TikuDbContext DbContext,
|
||||
ITenantSecretProtector TenantSecretProtector,
|
||||
ITenantExternalProviderConfigService ProviderConfigService,
|
||||
ICurrentAccessContext CurrentAccessContext,
|
||||
IBackgroundJobQueue BackgroundJobQueue,
|
||||
IBackgroundJobOperations BackgroundJobOperations);
|
||||
|
||||
internal abstract partial class CommerceAdministrationServiceBase(CommerceAdministrationDependencies dependencies)
|
||||
{
|
||||
protected TikuDbContext dbContext { get; } = dependencies.DbContext;
|
||||
protected ITenantSecretProtector tenantSecretProtector { get; } = dependencies.TenantSecretProtector;
|
||||
protected ITenantExternalProviderConfigService providerConfigService { get; } = dependencies.ProviderConfigService;
|
||||
protected ICurrentAccessContext currentAccessContext { get; } = dependencies.CurrentAccessContext;
|
||||
protected IBackgroundJobQueue backgroundJobQueue { get; } = dependencies.BackgroundJobQueue;
|
||||
protected IBackgroundJobOperations backgroundJobOperations { get; } = dependencies.BackgroundJobOperations;
|
||||
}
|
||||
@@ -4,7 +4,8 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class CommerceOrderAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), ICommerceOrderAdministrationService
|
||||
{
|
||||
public async Task<AdminOrderList> GetOrdersAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -5,7 +5,8 @@ using Tiku.Domain.Tenancy;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class PaymentConfigurationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IPaymentConfigurationService
|
||||
{
|
||||
public async Task<IReadOnlyCollection<TenantPaymentProviderItem>> GetPaymentAccountsAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -0,0 +1,170 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class ReconciliationAdministrationService
|
||||
{
|
||||
public async Task<TenantReconciliationItemList> GetReconciliationItemsAsync(
|
||||
CommerceAdminActor actor,
|
||||
Guid batchId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var batchExists = await dbContext.CommerceReconciliationBatches.AnyAsync(
|
||||
item => item.TenantId == actor.TenantId && item.Id == batchId,
|
||||
cancellationToken);
|
||||
if (!batchExists)
|
||||
throw new CommerceException("Reconciliation batch was not found.", "reconciliation_batch_not_found");
|
||||
|
||||
var items = await dbContext.CommerceReconciliationItems.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.BatchId == batchId)
|
||||
.OrderBy(item => item.RowNo)
|
||||
.Take(500)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new TenantReconciliationItemList(items);
|
||||
}
|
||||
|
||||
public async Task<TenantReconciliationIssueEventList> GetReconciliationIssueEventsAsync(
|
||||
CommerceAdminActor actor,
|
||||
Guid issueId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var issueExists = await dbContext.CommerceReconciliationIssues.AnyAsync(
|
||||
item => item.TenantId == actor.TenantId && item.Id == issueId,
|
||||
cancellationToken);
|
||||
if (!issueExists)
|
||||
throw new CommerceException("Reconciliation issue was not found.", "reconciliation_issue_not_found");
|
||||
|
||||
var events = await dbContext.CommerceReconciliationIssueEvents.AsNoTracking()
|
||||
.Where(item => item.TenantId == actor.TenantId && item.IssueId == issueId)
|
||||
.OrderBy(item => item.CreatedAt)
|
||||
.ToArrayAsync(cancellationToken);
|
||||
return new TenantReconciliationIssueEventList(events);
|
||||
}
|
||||
|
||||
public async Task<ReconciliationImportPreview> PreviewReconciliationImportAsync(
|
||||
CommerceAdminActor actor,
|
||||
PreviewReconciliationImportCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
return BuildImportPreview(command.Provider, command.Rows);
|
||||
}
|
||||
|
||||
public async Task<CommerceReconciliationBatch> ImportReconciliationAsync(
|
||||
CommerceAdminActor actor,
|
||||
ImportReconciliationCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var preview = BuildImportPreview(command.Provider, command.Rows);
|
||||
var batch = new CommerceReconciliationBatch
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
CreatedBy = actor.UserId,
|
||||
Provider = NormalizeProvider(command.Provider),
|
||||
BillDate = command.BillDate,
|
||||
BillType = command.BillType,
|
||||
Source = ReconciliationSource.ManualUpload,
|
||||
SourceName = command.SourceName.Trim(),
|
||||
SourceHash = preview.SourceHash,
|
||||
Status = preview.InvalidCount == 0
|
||||
? ReconciliationBatchStatus.Completed
|
||||
: ReconciliationBatchStatus.CompletedWithIssues,
|
||||
TotalCount = preview.TotalCount,
|
||||
MatchedCount = preview.TotalCount - preview.InvalidCount,
|
||||
MismatchCount = preview.InvalidCount,
|
||||
AmountCents = preview.AmountCents,
|
||||
RefundAmountCents = preview.RefundAmountCents,
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Metadata = JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
preview.PaymentCount,
|
||||
preview.RefundCount,
|
||||
preview.InvalidCount
|
||||
})
|
||||
};
|
||||
dbContext.CommerceReconciliationBatches.Add(batch);
|
||||
var rowNo = 0;
|
||||
foreach (var row in EnumerateImportRows(command.Rows))
|
||||
{
|
||||
rowNo++;
|
||||
var item = CreateReconciliationItem(actor.TenantId, batch.Id, rowNo, NormalizeProvider(command.Provider),
|
||||
row);
|
||||
dbContext.CommerceReconciliationItems.Add(item);
|
||||
if (item.MatchStatus != ReconciliationMatchStatus.Matched)
|
||||
dbContext.CommerceReconciliationIssues.Add(new CommerceReconciliationIssue
|
||||
{
|
||||
TenantId = actor.TenantId,
|
||||
BatchId = batch.Id,
|
||||
Provider = item.Provider,
|
||||
TransactionType = item.TransactionType,
|
||||
IssueNo = $"RC{DateTimeOffset.UtcNow:yyyyMMddHHmmss}{rowNo:0000}",
|
||||
MatchStatus = item.MatchStatus switch
|
||||
{
|
||||
ReconciliationMatchStatus.MissingLocal => ReconciliationIssueMatchStatus.MissingLocal,
|
||||
ReconciliationMatchStatus.MissingProvider => ReconciliationIssueMatchStatus.MissingProvider,
|
||||
ReconciliationMatchStatus.Duplicate => ReconciliationIssueMatchStatus.Duplicate,
|
||||
ReconciliationMatchStatus.StatusMismatch => ReconciliationIssueMatchStatus.StatusMismatch,
|
||||
_ => ReconciliationIssueMatchStatus.AmountMismatch
|
||||
},
|
||||
Severity = item.Severity,
|
||||
Status = ReconciliationIssueStatus.Open,
|
||||
OrderNo = item.OrderNo,
|
||||
RefundNo = item.RefundNo,
|
||||
ProviderTradeNo = item.ProviderTradeNo,
|
||||
ProviderRefundNo = item.ProviderRefundNo,
|
||||
AmountCents = item.AmountCents,
|
||||
RefundAmountCents = item.RefundAmountCents,
|
||||
Summary = item.IssueCode,
|
||||
CreatedBy = actor.UserId,
|
||||
Metadata = item.Details
|
||||
});
|
||||
}
|
||||
|
||||
await AddAuditAsync(actor, "commerce.reconciliation.imported", "commerce_reconciliation_batches", batch.Id,
|
||||
new { batch.Provider, batch.BillDate, batch.TotalCount }, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return batch;
|
||||
}
|
||||
|
||||
public async Task<BackgroundJobItem> RequestProviderBillJobAsync(
|
||||
CommerceAdminActor actor,
|
||||
RequestProviderBillJobCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
var job = await backgroundJobQueue.EnqueueAsync(
|
||||
new CreateBackgroundJobCommand(
|
||||
actor.TenantId,
|
||||
"commerce_reconciliation",
|
||||
JsonSerializer.SerializeToElement(new
|
||||
{
|
||||
provider = NormalizeProvider(command.Provider),
|
||||
command.BillDate,
|
||||
billType = command.BillType.ToString()
|
||||
}),
|
||||
command.RunAfter,
|
||||
5),
|
||||
cancellationToken);
|
||||
await AddAuditAsync(actor, "commerce.reconciliation.provider_bill_requested", "background_jobs", job.Id,
|
||||
new { command.Provider, command.BillDate, command.BillType }, cancellationToken);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return job;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<BackgroundJobItem>> GetProviderBillJobsAsync(
|
||||
CommerceAdminActor actor,
|
||||
CommerceAdminQuery query,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await AssertAdminAsync(actor, cancellationToken);
|
||||
return await backgroundJobOperations.ListAsync(actor.TenantId, "commerce_reconciliation",
|
||||
Math.Clamp(query.Limit ?? 50, 1, 200), cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed partial class ReconciliationAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IReconciliationAdministrationService
|
||||
{
|
||||
public async Task<TenantReconciliationBatchList> GetReconciliationBatchesAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Jobs;
|
||||
using Tiku.Domain.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class RefundAdministrationService
|
||||
{
|
||||
public async Task<CommerceRefundRequest> ProcessRefundNotificationAsync(
|
||||
Guid tenantId,
|
||||
RefundNotificationCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var provider = NormalizeProvider(command.Provider);
|
||||
var refund = await dbContext.CommerceRefundRequests.SingleOrDefaultAsync(
|
||||
item => item.TenantId == tenantId && item.RefundNo == command.RefundNo,
|
||||
cancellationToken) ?? throw new CommerceException("Refund request was not found.", "refund_not_found");
|
||||
var eventId = string.IsNullOrWhiteSpace(command.EventId)
|
||||
? $"{provider}:{command.RefundNo}:{command.Status}"
|
||||
: command.EventId.Trim();
|
||||
var duplicate = await dbContext.PaymentEvents.AnyAsync(
|
||||
item => item.TenantId == tenantId &&
|
||||
item.Provider == provider &&
|
||||
item.EventType == "refund" &&
|
||||
item.EventId == eventId,
|
||||
cancellationToken);
|
||||
if (duplicate) return refund;
|
||||
|
||||
dbContext.PaymentEvents.Add(new PaymentEvent
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Provider = provider,
|
||||
EventType = "refund",
|
||||
EventId = eventId,
|
||||
SignatureValid = true,
|
||||
Payload = JsonObjectOrDefault(command.Payload),
|
||||
ProcessedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
var fromStatus = refund.Status;
|
||||
if (fromStatus != command.Status && IsAllowedRefundTransition(fromStatus, command.Status))
|
||||
{
|
||||
refund.Status = command.Status;
|
||||
refund.ProviderRefundNo = string.IsNullOrWhiteSpace(command.ProviderRefundNo)
|
||||
? refund.ProviderRefundNo
|
||||
: command.ProviderRefundNo.Trim();
|
||||
if (command.Status == CommerceRefundStatus.Succeeded)
|
||||
{
|
||||
refund.SucceededAt = DateTimeOffset.UtcNow;
|
||||
await ApplyRefundToOrderAsync(refund, cancellationToken);
|
||||
}
|
||||
else if (command.Status == CommerceRefundStatus.Failed)
|
||||
{
|
||||
refund.FailedAt = DateTimeOffset.UtcNow;
|
||||
}
|
||||
|
||||
dbContext.CommerceRefundEvents.Add(new CommerceRefundEvent
|
||||
{
|
||||
TenantId = tenantId,
|
||||
RefundRequestId = refund.Id,
|
||||
FromStatus = fromStatus,
|
||||
ToStatus = command.Status,
|
||||
EventType = "provider_notify",
|
||||
Details = JsonObjectOrDefault(command.Payload)
|
||||
});
|
||||
}
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
return refund;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,8 @@ using Tiku.Infrastructure.Security;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed partial class RefundAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IRefundAdministrationService
|
||||
{
|
||||
public async Task<TenantRefundList> GetRefundsAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -17,7 +17,15 @@ internal static class CommerceModule
|
||||
internal static IServiceCollection AddCommerceModule(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<ICommerceService, CommerceService>();
|
||||
services.AddScoped<ICommerceAdminService, CommerceAdminService>();
|
||||
services.AddScoped<CommerceAdministrationDependencies>();
|
||||
services.AddScoped<IPaymentConfigurationService, PaymentConfigurationService>();
|
||||
services.AddScoped<ICommerceOrderAdministrationService, CommerceOrderAdministrationService>();
|
||||
services.AddScoped<IActivationCodeAdministrationService, ActivationCodeAdministrationService>();
|
||||
services.AddScoped<ICouponAdministrationService, CouponAdministrationService>();
|
||||
services.AddScoped<IRefundAdministrationService, RefundAdministrationService>();
|
||||
services.AddScoped<IReconciliationAdministrationService, ReconciliationAdministrationService>();
|
||||
services.AddScoped<ICommerceAdjustmentService, CommerceAdjustmentService>();
|
||||
services.AddScoped<IPointAdministrationService, PointAdministrationService>();
|
||||
services.AddScoped<IBackgroundJobHandler, CommerceReconciliationJobHandler>();
|
||||
services.AddScoped<IPointService, PointService>();
|
||||
services.AddScoped<IReferralQrcodeGenerator, ReferralQrcodeGenerator>();
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Tiku.Application.Commerce;
|
||||
using Tiku.Application.Points;
|
||||
using Tiku.Domain.Commerce;
|
||||
using Tiku.Infrastructure.Commerce;
|
||||
|
||||
namespace Tiku.Infrastructure.Commerce;
|
||||
namespace Tiku.Infrastructure.Points;
|
||||
|
||||
internal sealed partial class CommerceAdminService
|
||||
internal sealed class PointAdministrationService(CommerceAdministrationDependencies dependencies)
|
||||
: CommerceAdministrationServiceBase(dependencies), IPointAdministrationService
|
||||
{
|
||||
public async Task<TenantPointTaskList> GetPointTasksAsync(
|
||||
CommerceAdminActor actor,
|
||||
@@ -35,7 +35,6 @@ public sealed class ArchitectureBoundaryTests
|
||||
var root = FindRepositoryRoot();
|
||||
var legacyLineBudgets = new Dictionary<string, int>(StringComparer.Ordinal)
|
||||
{
|
||||
["CommerceAdminService"] = 1842,
|
||||
["LearningActivityService"] = 1781,
|
||||
["PlatformAdminService"] = 1719,
|
||||
["ContentManagementService"] = 1096,
|
||||
|
||||
Reference in New Issue
Block a user