From 00c94ab9b75b13c8bffc8bc59919b5fafdab2fb5 Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:41:26 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20refundRouter=EB=A5=BC=20purchaseRout?= =?UTF-8?q?er=20=EB=92=A4=EB=A1=9C=20=EC=9D=B4=EB=8F=99=ED=95=B4=20path=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=EC=9C=84=ED=97=98=20=EC=B0=A8=EB=8B=A8=20?= =?UTF-8?q?(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/index.ts에서 refundRouter가 purchaseRouter보다 먼저 동일 prefix (/api/prompts/purchases)에 마운트되어, 향후 purchaseRouter에 :id 동적 라우트가 추가될 경우 refundRouter의 :purchaseId 패턴에 가려질 위험. 순서를 뒤집어 purchaseRouter가 먼저 매치되도록 정정. 프론트 영향 없음 (현재 purchaseRouter는 정적 path만 사용 - /requests, /, /complete). --- src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0d0ad33..8db0119 100644 --- a/src/index.ts +++ b/src/index.ts @@ -138,8 +138,9 @@ app.use("/api/prompts", promptRoutes); // 페이플 PCD_RST_URL 서버 콜백 (urlencoded/json 자체 파싱) app.use("/api/prompts/purchases", purchaseWebhookRouter); -// 프롬프트 결제 라우터 -app.use("/api/prompts/purchases", refundRouter); +// 프롬프트 결제 라우터 — purchaseRouter가 동적 :id 라우트를 추후 추가해도 +// refundRouter 경로(:purchaseId/refund, :purchaseId/refund-eligibility)와 +// 충돌하지 않도록 refundRouter는 purchaseRouter 뒤에 마운트. app.use( "/api/prompts/purchases", express.text({ type: "text/plain" }), @@ -153,6 +154,7 @@ app.use( }, purchaseRouter ); +app.use("/api/prompts/purchases", refundRouter); // 채팅 라우터 app.use("/api/chat", chatRouter); From ca0dad6a74b83694f0f0b15d6a239ca8b8481277 Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:42:03 +0900 Subject: [PATCH 2/6] =?UTF-8?q?docs(swagger):=20prompt=20download=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8A=B8=20Swagger=20=EC=A0=84=EB=A9=B4=20?= =?UTF-8?q?=EC=9E=AC=EC=9E=91=EC=84=B1=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #485 환불 흐름 도입 후 prompt.download.route.ts의 Swagger가 실제 응답과 완전 불일치 상태였음. 전면 재작성. - GET /:promptId/downloads - 응답 schema를 실제 service 반환 형태({message, title, prompt, is_free, is_paid, statusCode})로 정정 - 환불된 구매 시 403 Refunded 응답 예시 추가 - 결제 미완료 시 403 Forbidden 응답 예시 추가 - 첫 다운로드 시 Purchase.downloaded_at 기록 부작용 설명 추가 - GET /downloads - 기존 JSDoc 부재 → 전체 신규 작성 - 응답 schema에 has_review/is_recent_review/userReview/imageUrls 등 포함 --- src/prompts/routes/prompt.download.route.ts | 128 +++++++++++++------- 1 file changed, 83 insertions(+), 45 deletions(-) diff --git a/src/prompts/routes/prompt.download.route.ts b/src/prompts/routes/prompt.download.route.ts index 4092027..09af072 100644 --- a/src/prompts/routes/prompt.download.route.ts +++ b/src/prompts/routes/prompt.download.route.ts @@ -8,40 +8,23 @@ const router = Router(); * @swagger * tags: * name: PromptDownload - * description: 프롬프트 다운로드 / 상세 내용 조회 API - */ - -/** - * @swagger - * components: - * schemas: - * PromptContent: - * type: object - * properties: - * prompt_id: - * type: integer - * example: 123 - * title: - * type: string - * example: "챗GPT로 마케팅 자동화하기" - * content: - * type: string - * example: "당신은 마케팅 전문가입니다. 이 프롬프트는..." - * download_count: - * type: integer - * example: 42 - * created_at: - * type: string - * format: date-time - * example: "2025-07-29T10:00:00Z" + * description: 프롬프트 다운로드 / 구매 목록 조회 API */ /** * @swagger * /api/prompts/{promptId}/downloads: * get: - * summary: 프롬프트 상세 내용 또는 다운로드 - * description: 로그인한 사용자가 프롬프트 상세 내용을 확인하거나 다운로드합니다. + * summary: 프롬프트 상세 내용 다운로드 + * description: | + * 로그인한 사용자가 구매한(또는 무료) 프롬프트의 실제 본문을 조회합니다. + * + * 유료 프롬프트의 경우: + * - 결제 완료(`Payment.status='Succeed'`)된 본인 구매만 허용 + * - 환불된 구매는 403 Refunded로 차단 + * - 첫 다운로드 시점에 `Purchase.downloaded_at`을 기록 — 이후 환불 불가 (#485) + * + * 무료 프롬프트는 Purchase row가 없으면 자동 생성. * tags: [PromptDownload] * security: * - jwt: [] @@ -49,38 +32,93 @@ const router = Router(); * - in: path * name: promptId * required: true - * description: 조회할 프롬프트 ID - * schema: - * type: integer + * schema: { type: integer } * responses: * 200: - * description: 프롬프트 내용 반환 성공 + * description: 다운로드 성공 * content: * application/json: * schema: - * $ref: '#/components/schemas/PromptContent' + * type: object + * properties: + * message: { type: string, example: 프롬프트 다운로드 완료 } + * title: { type: string, example: "챗GPT 마케팅 자동화" } + * prompt: { type: string, description: 프롬프트 본문 } + * is_free: { type: boolean } + * is_paid: { type: boolean } + * statusCode: { type: integer, example: 200 } * 401: - * description: 인증이 필요합니다 + * description: 로그인 필요 + * 403: + * description: 결제 미완료 또는 환불됨 * content: * application/json: * schema: * type: object * properties: - * error: - * type: string - * example: Unauthorized - * message: - * type: string - * example: 로그인이 필요합니다. - * statusCode: - * type: number - * example: 401 + * error: { type: string, description: "Forbidden | Refunded" } + * message: { type: string } + * statusCode: { type: integer, example: 403 } + * examples: + * notPaid: + * summary: 유료 프롬프트 미결제 + * value: { error: Forbidden, message: 해당 프롬프트는 무료가 아니며, 결제가 완료되지 않았습니다., statusCode: 403 } + * refunded: + * summary: 환불된 프롬프트 재다운로드 시도 + * value: { error: Refunded, message: 환불된 프롬프트는 다시 다운로드할 수 없습니다., statusCode: 403 } * 404: - * description: 프롬프트를 찾을 수 없음 + * description: 프롬프트 없음 * 500: * description: 서버 오류 */ router.get('/:promptId/downloads', authenticateJwt, PromptDownloadController.getPromptContent); + +/** + * @swagger + * /api/prompts/downloads: + * get: + * summary: 본인이 구매한 프롬프트 목록 조회 + * description: 로그인한 사용자가 다운로드(구매)한 프롬프트 목록을 최신순으로 반환합니다. 무료/유료 모두 포함. + * tags: [PromptDownload] + * security: + * - jwt: [] + * responses: + * 200: + * description: 목록 조회 성공 + * content: + * application/json: + * schema: + * type: object + * properties: + * message: { type: string, example: 프롬프트 다운로드 목록 조회 성공 } + * statusCode: { type: integer, example: 200 } + * data: + * type: array + * items: + * type: object + * properties: + * prompt_id: { type: integer } + * title: { type: string } + * description: { type: string, nullable: true } + * price: { type: integer } + * models: { type: array, items: { type: string } } + * imageUrls: { type: array, items: { type: string } } + * has_review: { type: boolean } + * is_recent_review: { type: boolean, description: 최근 30일 내 리뷰 작성 여부 } + * userNickname: { type: string } + * userProfileImageUrl: { type: string, nullable: true } + * userReview: + * type: object + * nullable: true + * properties: + * review_id: { type: integer } + * content: { type: string } + * rating: { type: number } + * 401: + * description: 로그인 필요 + * 500: + * description: 서버 오류 + */ router.get('/downloads', authenticateJwt, PromptDownloadController.getDownloadedPrompts); -export default router; \ No newline at end of file +export default router; From 2ea21afbf39aa30d9d7b406e474b767912bda10d Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:42:22 +0900 Subject: [PATCH 3/6] =?UTF-8?q?docs(swagger):=20/pending-amount=20stale=20?= =?UTF-8?q?=EB=A9=98=ED=8A=B8=20=EC=A0=95=EC=A0=95=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #479 시점 작성된 "Settlement.status를 'Succeed'로 업데이트하는 정산 완료 처리 흐름이 없어서..." 경고문이 #482/#483 settlement-sync 머지 이후로 사실과 달라짐. 실제 동작 (KST 08:00 cron + 환불 시 Refunded 전이)으로 교체. --- src/settlements/routes/settlement.route.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/settlements/routes/settlement.route.ts b/src/settlements/routes/settlement.route.ts index a2808bc..b7d6fc6 100644 --- a/src/settlements/routes/settlement.route.ts +++ b/src/settlements/routes/settlement.route.ts @@ -504,12 +504,11 @@ router.post("/register/business", authenticateJwt, registerBusiness); * get: * summary: 정산 예정 금액 조회 (대시보드) * description: | - * 로그인한 판매자의 정산 예정 금액(Settlement.status='Pending'인 행의 amount 합계)을 조회합니다. + * 로그인한 판매자의 정산 예정 금액(`Settlement.status='Pending'`인 행의 amount 합계)을 조회합니다. * 정산관리 화면 상단 대시보드에 노출. * - * ⚠️ 현재 코드에는 Settlement.status를 'Succeed'로 업데이트하는 정산 완료 처리 흐름이 없어서, - * 모든 미정산 거래의 누계가 반환됩니다. 별도 이슈에서 Payple 정산내역 조회와 연동한 동기화 흐름이 구현된 뒤에는 - * 실제 정산 예정분만 반환됩니다. + * 정산 완료 처리는 매일 KST 08:00에 동작하는 `settlement-sync` cron이 Payple 정산내역 조회 결과를 + * 바탕으로 `Pending → Succeed`로 전이합니다 (#482). 환불된 거래는 `Refunded` 상태가 되어 본 합계에서 제외됩니다 (#485). * tags: [Settlement] * security: * - jwt: [] From 4f34826ec49011c11b1e018fb7f097b927cdbb38 Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:43:09 +0900 Subject: [PATCH 4/6] =?UTF-8?q?docs(swagger):=20purchase=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20enum=EC=97=90=20Refunded=20=EC=B6=94=EA=B0=80=20+?= =?UTF-8?q?=20=EB=AF=B8=EB=B0=98=ED=99=98=20pg=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - POST /complete 응답 status enum에 'Refunded' 추가 (#485에서 Status enum 확장됐는데 본 응답엔 누락 — 프론트 switch가 default fallthrough될 위험) - GET / (결제내역) 응답 schema에서 'pg' 필드 제거 — Swagger엔 명시됐으나 실제 PurchaseHistoryService.list가 반환하지 않는 stale 명세 (결제내역에 환불 표시 추가는 별도 커밋에서 service/DTO 변경과 함께 진행) --- src/purchases/routes/purchase.route.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/purchases/routes/purchase.route.ts b/src/purchases/routes/purchase.route.ts index d28f123..c1c5827 100644 --- a/src/purchases/routes/purchase.route.ts +++ b/src/purchases/routes/purchase.route.ts @@ -101,11 +101,6 @@ router.post('/requests', authenticateJwt, PurchaseRequestController.requestPurch * purchased_at: * type: string * format: date-time - * pg: - * type: string - * description: 결제 제공자 (DB Enum) - * enum: [TOSSPAYMENTS, KAKAOPAY, TOSSPAY, NAVERPAY, SAMSUNGPAY, APPLEPAY, LPAY, PAYCO, SSG, PINPAY] - * nullable: true * statusCode: * type: integer */ @@ -152,7 +147,7 @@ router.get('/', authenticateJwt, PurchaseHistoryController.list); * type: object * properties: * message: { type: string } - * status: { type: string, enum: [Succeed, Failed, Pending] } + * status: { type: string, enum: [Succeed, Failed, Pending, Refunded] } * purchase_id: { type: integer } * statusCode: { type: integer } */ From 130dc4485d7bdff38f97eb7a18af5bcaa1096fb6 Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:45:45 +0900 Subject: [PATCH 5/6] =?UTF-8?q?docs(swagger):=20admin-seller=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=9D=91=EB=8B=B5=EC=97=90=20status=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #473에서 /account/detail에 status 추가됐는데 admin 상세 응답엔 누락되어 일관성 결여. - IndividualSellerDetail / BusinessSellerDetail DTO에 status 필드 추가 - getIndividualSellerDetail / getBusinessSellerDetail 서비스에서 account.status 노출 - admin-seller.route.ts swagger 두 응답에 status enum 명세 추가 현재 admin 상세 API는 status='APPROVED'만 조회하지만, 향후 PENDING/REJECTED도 조회 가능하게 확장될 경우 응답 형식 변경 없이 그대로 동작. --- src/settlements/dtos/admin-seller.dto.ts | 2 ++ src/settlements/routes/admin-seller.route.ts | 2 ++ src/settlements/services/admin-seller.service.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/settlements/dtos/admin-seller.dto.ts b/src/settlements/dtos/admin-seller.dto.ts index b8a4ec2..b6f46d9 100644 --- a/src/settlements/dtos/admin-seller.dto.ts +++ b/src/settlements/dtos/admin-seller.dto.ts @@ -85,6 +85,7 @@ export interface IndividualSellerDetail { name: string; email: string; registration_type: 'INDIVIDUAL'; + status: 'APPROVED' | 'PENDING' | 'REJECTED'; settlement_account: SettlementAccountSummary; created_at: Date; updated_at: Date; @@ -97,6 +98,7 @@ export interface BusinessSellerDetail { name: string; email: string; registration_type: 'BUSINESS'; + status: 'APPROVED' | 'PENDING' | 'REJECTED'; business_number: string | null; representative_name: string | null; company_name: string | null; diff --git a/src/settlements/routes/admin-seller.route.ts b/src/settlements/routes/admin-seller.route.ts index c0ae618..fe07413 100644 --- a/src/settlements/routes/admin-seller.route.ts +++ b/src/settlements/routes/admin-seller.route.ts @@ -454,6 +454,7 @@ router.get('/business', authenticateJwt, isAdmin, getBusinessSellerList); * name: { type: string, example: 홍길동 } * email: { type: string, example: gildong@example.com } * registration_type: { type: string, example: INDIVIDUAL } + * status: { type: string, enum: [APPROVED, PENDING, REJECTED], example: APPROVED } * settlement_account: * type: object * properties: @@ -517,6 +518,7 @@ router.get( * name: { type: string, example: 홍길동 } * email: { type: string, example: gildong@example.com } * registration_type: { type: string, example: BUSINESS } + * status: { type: string, enum: [APPROVED, PENDING, REJECTED], example: APPROVED } * business_number: { type: string, nullable: true, example: "123-45-67890" } * representative_name: { type: string, nullable: true, example: 홍길동 } * company_name: { type: string, nullable: true, example: 홍길동컴퍼니 } diff --git a/src/settlements/services/admin-seller.service.ts b/src/settlements/services/admin-seller.service.ts index 1fea150..2e6a9cd 100644 --- a/src/settlements/services/admin-seller.service.ts +++ b/src/settlements/services/admin-seller.service.ts @@ -252,6 +252,7 @@ export const getIndividualSellerDetail = async ( name: account.user.name, email: account.user.email, registration_type: 'INDIVIDUAL', + status: account.status, settlement_account: { bank_code: account.bank_code, account_number: account.account_number, @@ -281,6 +282,7 @@ export const getBusinessSellerDetail = async ( name: account.user.name, email: account.user.email, registration_type: 'BUSINESS', + status: account.status, business_number: account.business_number, representative_name: account.representative_name, company_name: account.company_name, From 529b95d25d638b19eb128da17a25d7c2b56c0add Mon Sep 17 00:00:00 2001 From: minij02 Date: Sat, 23 May 2026 04:46:04 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore:=20swagger.json=20=EC=9E=AC=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20(#494)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전 5개 커밋의 라우트 swagger 변경을 swagger.json에 반영. --- swagger.json | 240 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 182 insertions(+), 58 deletions(-) diff --git a/swagger.json b/swagger.json index 1471582..64287a8 100644 --- a/swagger.json +++ b/swagger.json @@ -3775,8 +3775,8 @@ }, "/api/prompts/{promptId}/downloads": { "get": { - "summary": "프롬프트 상세 내용 또는 다운로드", - "description": "로그인한 사용자가 프롬프트 상세 내용을 확인하거나 다운로드합니다.", + "summary": "프롬프트 상세 내용 다운로드", + "description": "로그인한 사용자가 구매한(또는 무료) 프롬프트의 실제 본문을 조회합니다.\n\n유료 프롬프트의 경우:\n- 결제 완료(`Payment.status='Succeed'`)된 본인 구매만 허용\n- 환불된 구매는 403 Refunded로 차단\n- 첫 다운로드 시점에 `Purchase.downloaded_at`을 기록 — 이후 환불 불가 (#485)\n\n무료 프롬프트는 Purchase row가 없으면 자동 생성.\n", "tags": [ "PromptDownload" ], @@ -3790,7 +3790,6 @@ "in": "path", "name": "promptId", "required": true, - "description": "조회할 프롬프트 ID", "schema": { "type": "integer" } @@ -3798,17 +3797,44 @@ ], "responses": { "200": { - "description": "프롬프트 내용 반환 성공", + "description": "다운로드 성공", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PromptContent" + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "프롬프트 다운로드 완료" + }, + "title": { + "type": "string", + "example": "챗GPT 마케팅 자동화" + }, + "prompt": { + "type": "string", + "description": "프롬프트 본문" + }, + "is_free": { + "type": "boolean" + }, + "is_paid": { + "type": "boolean" + }, + "statusCode": { + "type": "integer", + "example": 200 + } + } } } } }, "401": { - "description": "인증이 필요합니다", + "description": "로그인 필요" + }, + "403": { + "description": "결제 미완료 또는 환불됨", "content": { "application/json": { "schema": { @@ -3816,15 +3842,33 @@ "properties": { "error": { "type": "string", - "example": "Unauthorized" + "description": "Forbidden | Refunded" }, "message": { - "type": "string", - "example": "로그인이 필요합니다." + "type": "string" }, "statusCode": { - "type": "number", - "example": 401 + "type": "integer", + "example": 403 + } + } + }, + "examples": { + "notPaid": { + "summary": "유료 프롬프트 미결제", + "value": { + "error": "Forbidden", + "message": "해당 프롬프트는 무료가 아니며", + "결제가 완료되지 않았습니다.": null, + "statusCode": 403 + } + }, + "refunded": { + "summary": "환불된 프롬프트 재다운로드 시도", + "value": { + "error": "Refunded", + "message": "환불된 프롬프트는 다시 다운로드할 수 없습니다.", + "statusCode": 403 } } } @@ -3832,7 +3876,111 @@ } }, "404": { - "description": "프롬프트를 찾을 수 없음" + "description": "프롬프트 없음" + }, + "500": { + "description": "서버 오류" + } + } + } + }, + "/api/prompts/downloads": { + "get": { + "summary": "본인이 구매한 프롬프트 목록 조회", + "description": "로그인한 사용자가 다운로드(구매)한 프롬프트 목록을 최신순으로 반환합니다. 무료/유료 모두 포함.", + "tags": [ + "PromptDownload" + ], + "security": [ + { + "jwt": [] + } + ], + "responses": { + "200": { + "description": "목록 조회 성공", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "프롬프트 다운로드 목록 조회 성공" + }, + "statusCode": { + "type": "integer", + "example": 200 + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "prompt_id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "price": { + "type": "integer" + }, + "models": { + "type": "array", + "items": { + "type": "string" + } + }, + "imageUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "has_review": { + "type": "boolean" + }, + "is_recent_review": { + "type": "boolean", + "description": "최근 30일 내 리뷰 작성 여부" + }, + "userNickname": { + "type": "string" + }, + "userProfileImageUrl": { + "type": "string", + "nullable": true + }, + "userReview": { + "type": "object", + "nullable": true, + "properties": { + "review_id": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "rating": { + "type": "number" + } + } + } + } + } + } + } + } + } + } + }, + "401": { + "description": "로그인 필요" }, "500": { "description": "서버 오류" @@ -4822,23 +4970,6 @@ "purchased_at": { "type": "string", "format": "date-time" - }, - "pg": { - "type": "string", - "description": "결제 제공자 (DB Enum)", - "enum": [ - "TOSSPAYMENTS", - "KAKAOPAY", - "TOSSPAY", - "NAVERPAY", - "SAMSUNGPAY", - "APPLEPAY", - "LPAY", - "PAYCO", - "SSG", - "PINPAY" - ], - "nullable": true } } } @@ -4941,7 +5072,8 @@ "enum": [ "Succeed", "Failed", - "Pending" + "Pending", + "Refunded" ] }, "purchase_id": { @@ -6688,6 +6820,15 @@ "type": "string", "example": "INDIVIDUAL" }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "PENDING", + "REJECTED" + ], + "example": "APPROVED" + }, "settlement_account": { "type": "object", "properties": { @@ -6801,6 +6942,15 @@ "type": "string", "example": "BUSINESS" }, + "status": { + "type": "string", + "enum": [ + "APPROVED", + "PENDING", + "REJECTED" + ], + "example": "APPROVED" + }, "business_number": { "type": "string", "nullable": true, @@ -7853,7 +8003,7 @@ "/api/settlements/pending-amount": { "get": { "summary": "정산 예정 금액 조회 (대시보드)", - "description": "로그인한 판매자의 정산 예정 금액(Settlement.status='Pending'인 행의 amount 합계)을 조회합니다.\n정산관리 화면 상단 대시보드에 노출.\n\n⚠️ 현재 코드에는 Settlement.status를 'Succeed'로 업데이트하는 정산 완료 처리 흐름이 없어서,\n모든 미정산 거래의 누계가 반환됩니다. 별도 이슈에서 Payple 정산내역 조회와 연동한 동기화 흐름이 구현된 뒤에는\n실제 정산 예정분만 반환됩니다.\n", + "description": "로그인한 판매자의 정산 예정 금액(`Settlement.status='Pending'`인 행의 amount 합계)을 조회합니다.\n정산관리 화면 상단 대시보드에 노출.\n\n정산 완료 처리는 매일 KST 08:00에 동작하는 `settlement-sync` cron이 Payple 정산내역 조회 결과를\n바탕으로 `Pending → Succeed`로 전이합니다 (#482). 환불된 거래는 `Refunded` 상태가 되어 본 합계에서 제외됩니다 (#485).\n", "tags": [ "Settlement" ], @@ -8715,32 +8865,6 @@ }, "components": { "schemas": { - "PromptContent": { - "type": "object", - "properties": { - "prompt_id": { - "type": "integer", - "example": 123 - }, - "title": { - "type": "string", - "example": "챗GPT로 마케팅 자동화하기" - }, - "content": { - "type": "string", - "example": "당신은 마케팅 전문가입니다. 이 프롬프트는..." - }, - "download_count": { - "type": "integer", - "example": 42 - }, - "created_at": { - "type": "string", - "format": "date-time", - "example": "2025-07-29T10:00:00Z" - } - } - }, "PromptModel": { "type": "object", "properties": { @@ -8819,7 +8943,7 @@ }, { "name": "PromptDownload", - "description": "프롬프트 다운로드 / 상세 내용 조회 API" + "description": "프롬프트 다운로드 / 구매 목록 조회 API" }, { "name": "PromptLike",