From 7c6a6764a9bddb89d379e0b261a0f7c2ff969802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=AF=BC=EA=B7=A0?= <97932282+skyblue1232@users.noreply.github.com> Date: Fri, 1 May 2026 03:33:37 +0900 Subject: [PATCH 1/2] feat/owner-upgrade-apis --- .../customer/src/app/payment/success/page.tsx | 30 ++++++++-- apps/customer/src/shared/api/api.ts | 4 +- .../useCompleteAdminSettlementMutation.ts | 12 ++++ apps/owner/src/app/signup/business/page.tsx | 38 +++++------- .../auth/useUpgradeToStoreManagerMutation.ts | 12 ++++ .../auth/useVerifyBusinessMutation.ts | 6 +- packages/api/src/domains/admin-settlement.ts | 59 +++++++++++++++++++ packages/api/src/domains/owner.ts | 16 ----- packages/api/src/index.ts | 2 + packages/api/src/models/admin-settlement.ts | 16 +++++ packages/api/src/models/owner.ts | 21 ++----- 11 files changed, 153 insertions(+), 63 deletions(-) create mode 100644 apps/customer/src/shared/queries/mutation/admin-settlement/useCompleteAdminSettlementMutation.ts create mode 100644 apps/owner/src/shared/queries/mutation/auth/useUpgradeToStoreManagerMutation.ts create mode 100644 packages/api/src/domains/admin-settlement.ts create mode 100644 packages/api/src/models/admin-settlement.ts diff --git a/apps/customer/src/app/payment/success/page.tsx b/apps/customer/src/app/payment/success/page.tsx index b0027d3..f1c979e 100644 --- a/apps/customer/src/app/payment/success/page.tsx +++ b/apps/customer/src/app/payment/success/page.tsx @@ -1,9 +1,10 @@ "use client"; -import { Suspense, useEffect, useMemo, useState } from "react"; +import { Suspense, useEffect, useMemo, useRef, useState } from "react"; import { useSearchParams } from "next/navigation"; import type { StoreRandomBoxRespDTO, StoreRespDTO } from "@compasser/api"; import { useApproveKakaoPayMutation } from "@/shared/queries/mutation/payment/useApproveKakaoPayMutation"; +import { useCompleteAdminSettlementMutation } from "@/shared/queries/mutation/admin-settlement/useCompleteAdminSettlementMutation"; import PurchaseCompleteModal from "@/app/(tabs)/main/store/[id]/purchase/_components/PurchaseCompleteModal"; interface PendingPayment { @@ -15,7 +16,12 @@ interface PendingPayment { function PaymentSuccessContent() { const searchParams = useSearchParams(); + const approveKakaoPayMutation = useApproveKakaoPayMutation(); + const completeAdminSettlementMutation = useCompleteAdminSettlementMutation(); + + const hasRequestedRef = useRef(false); + const [pendingPayment, setPendingPayment] = useState( null, ); @@ -29,10 +35,14 @@ function PaymentSuccessContent() { const pgToken = searchParams.get("pg_token"); useEffect(() => { + if (hasRequestedRef.current) return; + const savedPayment = sessionStorage.getItem("pendingPayment"); if (!savedPayment || !reservationId || !pgToken) return; + hasRequestedRef.current = true; + const parsedPayment = JSON.parse(savedPayment) as PendingPayment; setPendingPayment(parsedPayment); @@ -44,12 +54,24 @@ function PaymentSuccessContent() { }, { onSuccess: () => { - sessionStorage.removeItem("pendingPayment"); - setIsCompleteModalOpen(true); + completeAdminSettlementMutation.mutate( + { + storeId: parsedPayment.store.storeId, + body: { + reservationIds: [reservationId], + }, + }, + { + onSettled: () => { + sessionStorage.removeItem("pendingPayment"); + setIsCompleteModalOpen(true); + }, + }, + ); }, }, ); - }, [reservationId, pgToken, approveKakaoPayMutation]); + }, [reservationId, pgToken]); if (approveKakaoPayMutation.isPending) { return
결제 승인 처리 중...
; diff --git a/apps/customer/src/shared/api/api.ts b/apps/customer/src/shared/api/api.ts index e643a7f..3f6c68f 100644 --- a/apps/customer/src/shared/api/api.ts +++ b/apps/customer/src/shared/api/api.ts @@ -9,6 +9,7 @@ import { createMemberModule, createOrderModule, createPaymentModule, + createAdminSettlementModule, } from "@compasser/api"; const tokenStore: TokenStore = { @@ -44,4 +45,5 @@ export const authModule = createAuthModule(compasserApi); export const memberModule = createMemberModule(compasserApi); export const storeModule = createStoreModule(compasserApi); export const orderModule = createOrderModule(compasserApi); -export const paymentModule = createPaymentModule(compasserApi); \ No newline at end of file +export const paymentModule = createPaymentModule(compasserApi); +export const adminSettlementModule = createAdminSettlementModule(compasserApi); \ No newline at end of file diff --git a/apps/customer/src/shared/queries/mutation/admin-settlement/useCompleteAdminSettlementMutation.ts b/apps/customer/src/shared/queries/mutation/admin-settlement/useCompleteAdminSettlementMutation.ts new file mode 100644 index 0000000..d4fb747 --- /dev/null +++ b/apps/customer/src/shared/queries/mutation/admin-settlement/useCompleteAdminSettlementMutation.ts @@ -0,0 +1,12 @@ +"use client"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { adminSettlementModule } from "@/shared/api/api"; + +export const useCompleteAdminSettlementMutation = () => { + const queryClient = useQueryClient(); + + return useMutation({ + ...adminSettlementModule.mutations.completeSettlement(queryClient), + }); +}; \ No newline at end of file diff --git a/apps/owner/src/app/signup/business/page.tsx b/apps/owner/src/app/signup/business/page.tsx index f9974b3..b4ea32e 100644 --- a/apps/owner/src/app/signup/business/page.tsx +++ b/apps/owner/src/app/signup/business/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Input, Button } from "@compasser/design-system"; -import { useVerifyBusinessMutation } from "@/shared/queries/mutation/auth/useVerifyBusinessMutation"; +import { useUpgradeToStoreManagerMutation } from "@/shared/queries/mutation/auth/useUpgradeToStoreManagerMutation"; import { useOwnerSignupStore } from "@/shared/stores/ownerSignup.store"; import { normalizeBusinessNumber, @@ -13,7 +13,7 @@ import { export default function BusinessSignupPage() { const router = useRouter(); - const verifyMutation = useVerifyBusinessMutation(); + const upgradeMutation = useUpgradeToStoreManagerMutation(); const signupCompleted = useOwnerSignupStore((s) => s.signupCompleted); const email = useOwnerSignupStore((s) => s.email); @@ -65,26 +65,20 @@ export default function BusinessSignupPage() { return; } - verifyMutation.mutate( - { - businessLicenseNumber: value, - email, + upgradeMutation.mutate(undefined, { + onSuccess: (res) => { + if (res.alreadyUpgraded) { + setError("이미 사업자 등록이 완료된 계정입니다."); + return; + } + + setBusinessCompleted(); + router.push("/signup/register"); }, - { - onSuccess: (res) => { - if (res.alreadyUpgraded) { - setError("이미 사업자 등록이 완료된 계정입니다."); - return; - } - - setBusinessCompleted(); - router.push("/signup/register"); - }, - onError: () => { - setError("사업자 번호 인증에 실패했습니다."); - }, + onError: () => { + setError("점장 승격 처리에 실패했습니다."); }, - ); + }); }; return ( @@ -115,9 +109,9 @@ export default function BusinessSignupPage() { size="lg" variant="primary" onClick={handleNext} - disabled={verifyMutation.isPending} + disabled={upgradeMutation.isPending} > - {verifyMutation.isPending ? "확인 중..." : "다음으로"} + {upgradeMutation.isPending ? "확인 중..." : "다음으로"} diff --git a/apps/owner/src/shared/queries/mutation/auth/useUpgradeToStoreManagerMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useUpgradeToStoreManagerMutation.ts new file mode 100644 index 0000000..0cb54a8 --- /dev/null +++ b/apps/owner/src/shared/queries/mutation/auth/useUpgradeToStoreManagerMutation.ts @@ -0,0 +1,12 @@ +"use client"; + +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { ownerModule } from "@/shared/api/api"; + +export const useUpgradeToStoreManagerMutation = () => { + const queryClient = useQueryClient(); + + return useMutation({ + ...ownerModule.mutations.upgradeToStoreManager(queryClient), + }); +}; \ No newline at end of file diff --git a/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts b/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts index 7112468..e667621 100644 --- a/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts +++ b/apps/owner/src/shared/queries/mutation/auth/useVerifyBusinessMutation.ts @@ -6,7 +6,7 @@ import { ownerModule } from "@/shared/api/api"; export const useVerifyBusinessMutation = () => { const queryClient = useQueryClient(); - return useMutation( - ownerModule.mutations.verifyBizAndUpgrade(queryClient), - ); + return useMutation({ + ...ownerModule.mutations.verifyBizAndUpgrade(queryClient), + }); }; \ No newline at end of file diff --git a/packages/api/src/domains/admin-settlement.ts b/packages/api/src/domains/admin-settlement.ts new file mode 100644 index 0000000..a4868c8 --- /dev/null +++ b/packages/api/src/domains/admin-settlement.ts @@ -0,0 +1,59 @@ +// packages/api/src/domains/admin-settlement.ts + +import type { QueryClient } from "@tanstack/react-query"; + +import { createMutationWithCache } from "../core/mutation"; +import type { CompasserApi } from "../core/types"; +import type { + AdminSettlementCompleteReqDTO, + AdminSettlementCompleteResponse, +} from "../models/admin-settlement"; + +export interface CompleteAdminSettlementParams { + storeId: number; + body: AdminSettlementCompleteReqDTO; +} + +export const createAdminSettlementModule = (api: CompasserApi) => { + const keys = { + all: ["admin-settlement"] as const, + complete: (storeId: number) => [...keys.all, "complete", storeId] as const, + }; + + const requests = { + completeSettlement: async ({ + storeId, + body, + }: CompleteAdminSettlementParams) => { + const { data } = + await api.privateClient.post( + `/admin/settlements/${storeId}/complete`, + body, + ); + + return data; + }, + }; + + const mutations = { + completeSettlement: (queryClient: QueryClient) => + createMutationWithCache({ + queryClient, + mutationKey: [...keys.all, "complete"], + mutationFn: requests.completeSettlement, + getActions: (response, variables) => [ + { + type: "set", + queryKey: keys.complete(variables.storeId), + value: response, + }, + { + type: "invalidate", + queryKey: keys.all, + }, + ], + }), + }; + + return { keys, requests, mutations }; +}; \ No newline at end of file diff --git a/packages/api/src/domains/owner.ts b/packages/api/src/domains/owner.ts index 11102ad..5f48b54 100644 --- a/packages/api/src/domains/owner.ts +++ b/packages/api/src/domains/owner.ts @@ -8,7 +8,6 @@ import type { ReservationListResponse, ReservationReqDTO, ReservationResponse, - SettlementPreviewDTO, } from "../models/owner"; export interface ReservationDecisionParams { @@ -23,8 +22,6 @@ export const createOwnerModule = (api: CompasserApi) => { reservations: () => [...keys.all, "reservations"] as const, pendingReservations: () => [...keys.reservations(), "pending"] as const, processedReservations: () => [...keys.reservations(), "processed"] as const, - settlements: () => [...keys.all, "settlements"] as const, - settlementPreview: () => [...keys.settlements(), "preview"] as const, }; const requests = { @@ -74,13 +71,6 @@ export const createOwnerModule = (api: CompasserApi) => { ); return data; }, - - getSettlementPreview: async (): Promise => { - const { data } = await api.privateClient.get( - "/owners/my-store/settlements/preview", - ); - return data; - }, }; const queries = { @@ -95,12 +85,6 @@ export const createOwnerModule = (api: CompasserApi) => { queryKey: keys.processedReservations(), queryFn: async () => (await requests.getProcessedReservations()).data, }), - - settlementPreview: () => - queryOptions({ - queryKey: keys.settlementPreview(), - queryFn: async () => (await requests.getSettlementPreview()), - }), }; const mutations = { diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 25399c9..32d5350 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -13,6 +13,7 @@ export * from "./models/random-box"; export * from "./models/store"; export * from "./models/storeManager"; export * from "./models/store-image"; +export * from "./models/admin-settlement"; export * from "./domains/auth"; export * from "./domains/health"; export * from "./domains/member"; @@ -23,4 +24,5 @@ export * from "./domains/random-box"; export * from "./domains/store"; export * from "./domains/store-image"; export * from "./domains/store-manager"; +export * from "./domains/admin-settlement"; export * from "./domains/index"; \ No newline at end of file diff --git a/packages/api/src/models/admin-settlement.ts b/packages/api/src/models/admin-settlement.ts new file mode 100644 index 0000000..225fd04 --- /dev/null +++ b/packages/api/src/models/admin-settlement.ts @@ -0,0 +1,16 @@ +import type { ApiResponse } from "../core/types"; + +export interface AdminSettlementCompleteReqDTO { + reservationIds: number[]; +} + +export interface AdminSettlementCompleteRespDTO { + storeId: number; + storeName: string; + count: number; + totalAmount: number; + message: string; +} + +export type AdminSettlementCompleteResponse = + ApiResponse; \ No newline at end of file diff --git a/packages/api/src/models/owner.ts b/packages/api/src/models/owner.ts index c9a1c24..92842b4 100644 --- a/packages/api/src/models/owner.ts +++ b/packages/api/src/models/owner.ts @@ -2,8 +2,10 @@ import type { ApiResponse } from "../core/types"; import type { ReservationStatus, RoleType } from "./common"; export interface BusinessLicenseVerifyReqDTO { - businessLicenseNumber?: string; - email?: string; + businessLicenseNumber: string; + ownerName: string; + startDate: string; + businessName: string; } export interface OwnerUpgradeRespDTO { @@ -39,20 +41,5 @@ export interface ReservationListDTO { count: number; } -export interface SettlementPreviewReservationDTO { - reservationId: number; - memberId: number; - totalPrice: number; - createdAt: string; -} - -export interface SettlementPreviewDTO { - storeId: number; - storeName: string; - count: number; - totalAmount: number; - reservations: SettlementPreviewReservationDTO[]; -} - export type ReservationResponse = ApiResponse; export type ReservationListResponse = ApiResponse; \ No newline at end of file From f52f69813c791038564b3e6b303b051658dcb680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B3=A0=EB=AF=BC=EA=B7=A0?= <97932282+skyblue1232@users.noreply.github.com> Date: Fri, 1 May 2026 03:48:02 +0900 Subject: [PATCH 2/2] chore/owner-apis --- packages/api/src/domains/owner.ts | 16 ++++++++++++++++ packages/api/src/models/owner.ts | 21 +++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/api/src/domains/owner.ts b/packages/api/src/domains/owner.ts index 5f48b54..11102ad 100644 --- a/packages/api/src/domains/owner.ts +++ b/packages/api/src/domains/owner.ts @@ -8,6 +8,7 @@ import type { ReservationListResponse, ReservationReqDTO, ReservationResponse, + SettlementPreviewDTO, } from "../models/owner"; export interface ReservationDecisionParams { @@ -22,6 +23,8 @@ export const createOwnerModule = (api: CompasserApi) => { reservations: () => [...keys.all, "reservations"] as const, pendingReservations: () => [...keys.reservations(), "pending"] as const, processedReservations: () => [...keys.reservations(), "processed"] as const, + settlements: () => [...keys.all, "settlements"] as const, + settlementPreview: () => [...keys.settlements(), "preview"] as const, }; const requests = { @@ -71,6 +74,13 @@ export const createOwnerModule = (api: CompasserApi) => { ); return data; }, + + getSettlementPreview: async (): Promise => { + const { data } = await api.privateClient.get( + "/owners/my-store/settlements/preview", + ); + return data; + }, }; const queries = { @@ -85,6 +95,12 @@ export const createOwnerModule = (api: CompasserApi) => { queryKey: keys.processedReservations(), queryFn: async () => (await requests.getProcessedReservations()).data, }), + + settlementPreview: () => + queryOptions({ + queryKey: keys.settlementPreview(), + queryFn: async () => (await requests.getSettlementPreview()), + }), }; const mutations = { diff --git a/packages/api/src/models/owner.ts b/packages/api/src/models/owner.ts index 92842b4..c9a1c24 100644 --- a/packages/api/src/models/owner.ts +++ b/packages/api/src/models/owner.ts @@ -2,10 +2,8 @@ import type { ApiResponse } from "../core/types"; import type { ReservationStatus, RoleType } from "./common"; export interface BusinessLicenseVerifyReqDTO { - businessLicenseNumber: string; - ownerName: string; - startDate: string; - businessName: string; + businessLicenseNumber?: string; + email?: string; } export interface OwnerUpgradeRespDTO { @@ -41,5 +39,20 @@ export interface ReservationListDTO { count: number; } +export interface SettlementPreviewReservationDTO { + reservationId: number; + memberId: number; + totalPrice: number; + createdAt: string; +} + +export interface SettlementPreviewDTO { + storeId: number; + storeName: string; + count: number; + totalAmount: number; + reservations: SettlementPreviewReservationDTO[]; +} + export type ReservationResponse = ApiResponse; export type ReservationListResponse = ApiResponse; \ No newline at end of file