| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- using System.Security.Claims;
- using MediatR;
- using Web.Api.Common;
- using Web.Api.Extensions;
- namespace Web.Api.Endpoints.V1.Purchases;
- internal sealed class Register : IEndpoint
- {
- public void MapEndpoint(IEndpointRouteBuilder app)
- {
- app.MapPost("v1/purchases", async (
- Application.Features.Api.V1.Purchases.Register.Command body,
- ClaimsPrincipal user,
- ISender sender,
- CancellationToken ct
- ) => {
- var applicationID = user.GetApplicationID();
- if (applicationID is null)
- {
- return ApiResponse.Fail(StatusCodes.Status403Forbidden, "결제 등록은 OAuth2 앱 토큰 전용입니다. (PAT 사용 불가)");
- }
- var command = body with { ApplicationID = applicationID.Value };
- var result = await sender.Send(command, ct);
- return result.Match(
- data => ApiResponse.Created(data),
- CustomResults.Problem
- );
- })
- .WithTags("결제 보고")
- .WithGroupName("public")
- .WithName("RegisterPurchase")
- .WithSummary("결제 등록")
- .WithDescription("""
- 게임 내 결제를 DPOT 에 보고합니다. 검증 통과 시 **보류(Pending) 원장**이 생성되고,
- 등록 시점 +14일(달력일, 주말·공휴일 포함) 후 확정 배치가 채널 소유 회원의 지갑에
- 판매 수수료(`orderPrice × 게임별 수수료율`)를 적립합니다.
- ### 필수 scope
- - `write:purchases`
- ### 인증 제한
- - **OAuth2 Client Credentials 앱 토큰 전용** — PAT 으로 호출 시 `403`
- ### 요청 본문
- | 필드 | 타입 | 필수 | 설명 |
- |---|---|---|---|
- | `channelCode` | string | ✅ | 채널 후원 코드 (4~7자 영문+숫자, 게임 내 유저 입력값) |
- | `orderID` | string(255) | ✅ | **마켓 거래 ID 원문** — Google `GPA.xxxx-xxxx-xxxx-xxxxx`, Apple transactionId 등. 가공·축약 금지 |
- | `marketplace` | int | ✅ | 1=구글, 2=애플, 3=MS, 4=갤럭시, 5=원스토어, 6=기타 |
- | `gameCode` | string | ✅ | DPOT 에 등록된 게임 코드 |
- | `orderPrice` | int | ✅ | 결제 금액 (KRW, 1 이상) |
- | `productID` | string(100) | 권장 | 인앱 상품 SKU (예: `diamond_100`) — 금액 검증에 사용 |
- | `subID` | string(100) | 선택 | 파트너 측 보조 식별자 |
- ### 멱등성
- 동일 (앱, `marketplace`, `orderID`) 조합은 1회만 등록 가능 — 재시도 시 `409`.
- ### 응답 (201)
- `commissionAmount`(채널 수수료), `status`(`Pending`), `confirmDueAt`(확정 예정 시각) 포함.
- ### 에러
- - `400` — 필드 누락/형식 오류
- - `403` — PAT 호출 / 앱 비활성
- - `404` — 후원 코드(`Channel.NotFound`) 또는 게임(`Game.NotFound`) 없음
- - `409` — 중복 주문 (`Purchase.Duplicate`)
- """)
- .Produces<ApiResponse>(StatusCodes.Status201Created)
- .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");
- }
- }
|