| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 |
- using System.Security.Claims;
- using MediatR;
- using Web.Api.Common;
- using Web.Api.Extensions;
- namespace Web.Api.Endpoints.V1.Purchases;
- internal sealed class Cancel : IEndpoint
- {
- public void MapEndpoint(IEndpointRouteBuilder app)
- {
- app.MapPost("v1/purchases/{orderID}/cancel", async (
- string orderID,
- ClaimsPrincipal user,
- ISender sender,
- CancellationToken ct,
- int? marketplace = null
- ) => {
- var applicationID = user.GetApplicationID();
- if (applicationID is null)
- {
- return ApiResponse.Fail(StatusCodes.Status403Forbidden, "결제 취소는 OAuth2 앱 토큰 전용입니다. (PAT 사용 불가)");
- }
- var command = new Application.Features.Api.V1.Purchases.Cancel.Command(applicationID.Value, orderID, marketplace);
- var result = await sender.Send(command, ct);
- return result.Match(
- data => ApiResponse.Ok(data),
- CustomResults.Problem
- );
- })
- .WithTags("결제 보고")
- .WithGroupName("public")
- .WithName("CancelPurchase")
- .WithSummary("결제 취소")
- .WithDescription("""
- 등록된 결제를 취소합니다. **등록한 앱만 취소 가능**합니다.
- - **보류(Pending) 중 취소**: 지갑 변동 없이 취소 처리 (`recoveredAmount=0`)
- - **확정(Confirmed) 후 취소**: 적립된 채널 수수료를 회수. 채널주가 이미 출금해 잔액이
- 부족하면 가용분만 회수하고 부족분을 `shortfallAmount` 로 응답 (DPOT 운영진 검토 대상)
- ### 필수 scope
- - `write:purchases`
- ### 인증 제한
- - **OAuth2 Client Credentials 앱 토큰 전용** — PAT 으로 호출 시 `403`
- ### 경로/쿼리
- | 이름 | 위치 | 필수 | 설명 |
- |---|---|---|---|
- | `orderID` | path | ✅ | 등록 시 사용한 마켓 거래 ID |
- | `marketplace` | query | 조건 | 동일 orderID 가 여러 마켓에 등록된 경우만 필수 |
- ### 에러
- - `400` — orderID 누락 / 마켓 모호 (`Purchase.AmbiguousOrderID`)
- - `403` — PAT 호출
- - `404` — 등록 내역 없음 (타 앱 등록 건 포함 — 조회 불가)
- - `409` — 이미 취소됨 (`Purchase.AlreadyCanceled`)
- """)
- .Produces<ApiResponse>(StatusCodes.Status200OK)
- .ProducesProblem(StatusCodes.Status400BadRequest)
- .ProducesProblem(StatusCodes.Status401Unauthorized)
- .ProducesProblem(StatusCodes.Status403Forbidden)
- .ProducesProblem(StatusCodes.Status404NotFound)
- .ProducesProblem(StatusCodes.Status409Conflict)
- .RequireAuthorization(policy => policy
- .AddAuthenticationSchemes("ApiKey", "OAuth2Bearer")
- .RequireAuthenticatedUser())
- .RequireScope("write:purchases");
- }
- }
|