From a15f185489b52a491143e1eda7cc93b89bf2bb1d Mon Sep 17 00:00:00 2001 From: yeonhee7935 Date: Tue, 25 Feb 2025 22:36:58 +0900 Subject: [PATCH 01/24] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=EC=83=81=ED=83=9C=20=EC=A1=B0=ED=9A=8C=20api?= =?UTF-8?q?=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/useGetOrderStatus.ts | 21 +++++ src/api/useGetOrdersDetail.ts | 2 +- .../detail/[id]/_components/OrderStatus.tsx | 43 +++++----- src/app/orders/detail/[id]/page.tsx | 10 +-- src/mocks/handlers/index.ts | 34 ++++++++ src/mocks/handlers/orders.ts | 84 +++++++++++++++++++ src/mocks/index.ts | 2 +- 7 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 src/api/useGetOrderStatus.ts create mode 100644 src/mocks/handlers/orders.ts diff --git a/src/api/useGetOrderStatus.ts b/src/api/useGetOrderStatus.ts new file mode 100644 index 00000000..20469e64 --- /dev/null +++ b/src/api/useGetOrderStatus.ts @@ -0,0 +1,21 @@ +import { api } from '@/lib/api' +import { useQuery, useQueryClient } from '@tanstack/react-query' + +export type OrderStatus = 'NEW' | 'ONGOING' | 'DONE' | 'REFUSE' | 'CANCELED' + +const useGetOrderStatus = (orderId?: string) => { + const { data: status, isSuccess } = useQuery({ + queryKey: ['orderStatus', orderId], + queryFn: async () => { + return await api.get(`orders/${orderId}/status`) + }, + refetchInterval: (data) => { + const shouldRefetch = ['NEW', 'ONGOING'].includes(data.state.data as OrderStatus) + return shouldRefetch ? 5000 : false + }, + }) + + return { status, isSuccess } +} + +export default useGetOrderStatus diff --git a/src/api/useGetOrdersDetail.ts b/src/api/useGetOrdersDetail.ts index e159fe79..69853584 100644 --- a/src/api/useGetOrdersDetail.ts +++ b/src/api/useGetOrdersDetail.ts @@ -1,7 +1,7 @@ import { api } from '@/lib/api' import { useQuery, useQueryClient } from '@tanstack/react-query' -interface OrdersDetail { +export interface OrdersDetail { orderId: string status: { code: string diff --git a/src/app/orders/detail/[id]/_components/OrderStatus.tsx b/src/app/orders/detail/[id]/_components/OrderStatus.tsx index daf852e5..19d4d99d 100644 --- a/src/app/orders/detail/[id]/_components/OrderStatus.tsx +++ b/src/app/orders/detail/[id]/_components/OrderStatus.tsx @@ -4,49 +4,44 @@ import { Progress } from '@/components/shadcn/progress' import { useEffect, useState } from 'react' type OrderStatusProps = { - orderStatus?: string | null + orderStatus: string } const OrderStatus: React.FC = ({ orderStatus }) => { - const [status, setStatus] = useState(orderStatus ?? '') + const [title, setTitle] = useState('') const [subTitle, setSubTitle] = useState('') const [value, setValue] = useState(0) useEffect(() => { - switch (status) { - case '주문거절': - setTitle('주문이 거절되었습니다.') - setSubTitle('다음에 다시 이용해 주세요') - setValue(0) - break - case '주문접수': + switch (orderStatus) { + case 'NEW': setTitle('주문이 접수되었습니다.') setSubTitle('확인 중입니다. 잠시만 기다려 주세요!') - setValue(0) + setValue(20) break - case '주문수락': + case 'ONGOING': setTitle('주문을 수락했어요') setSubTitle('잠시 후 도착 예정 시간을 알려드릴게요.') - setValue(20) - break - case '배달진행중': - setTitle('음식이 배달 중입니다') - setSubTitle('라이더가 빠르게 가는 중입니다. 기다려 주세요!') setValue(50) break - case '배달완료': + case 'DONE': setTitle('배달을 완료했어요') setSubTitle('맛있게 드시고 리뷰를 남겨주세요!') setValue(100) break + case 'REFUSE': + setTitle('주문이 거절되었습니다.') + setSubTitle('다음에 다시 이용해 주세요') + setValue(0) + break default: setTitle('') setSubTitle('') setValue(0) break } - }, [status]) + }, [orderStatus]) return (
@@ -58,14 +53,14 @@ const OrderStatus: React.FC = ({ orderStatus }) => {
-
- 주문 수락 +
+ 주문접수
-
- 배달 진행중 +
+ 조리중
-
- 배달 완료 +
+ 배달완료
diff --git a/src/app/orders/detail/[id]/page.tsx b/src/app/orders/detail/[id]/page.tsx index df02ed24..5668ad23 100644 --- a/src/app/orders/detail/[id]/page.tsx +++ b/src/app/orders/detail/[id]/page.tsx @@ -5,21 +5,15 @@ import OrderStatus from '@/app/orders/detail/[id]/_components/OrderStatus' import OrderList from '@/app/orders/detail/[id]/_components/OrderList' import { usePathname } from 'next/navigation' import useGetOrdersDetail from '@/api/useGetOrdersDetail' -import { useEffect, useState } from 'react' +import useGetOrderStatus from '@/api/useGetOrderStatus' const OrderDetailPage = () => { const path = usePathname() const { ordersDetail, resetGetOrdersDetail, isSuccess } = useGetOrdersDetail( path.split('/').pop() ) + const {status} = useGetOrderStatus(path.split('/').pop()) - const [status, setStatus] = useState(null) - - useEffect(() => { - if (ordersDetail) { - setStatus(ordersDetail.status.desc) - } - }, [ordersDetail]) return (
diff --git a/src/mocks/handlers/index.ts b/src/mocks/handlers/index.ts index 1d41837c..9ed4fab3 100644 --- a/src/mocks/handlers/index.ts +++ b/src/mocks/handlers/index.ts @@ -1,6 +1,7 @@ import BANNER_MOCK_DATA from '@/constants/banners' import STORE_MOCK_DATA from '@/constants/stores' import { delay, http, HttpResponse, passthrough } from 'msw' +import { DUMMY_ORDER_LIST } from './orders' // API 엔드포인트 예시 export const handlers = [ @@ -171,6 +172,39 @@ export const handlers = [ return passthrough() }), + http.get('*/api/v1/orders/:orderId', ({ request, params }) => { + const orderId = decodeURIComponent(params.orderId as string) + + const orderList = DUMMY_ORDER_LIST.filter((order) => order.orderId === orderId) + return HttpResponse.json({ + status: 200, + message: 'success', + data: orderList.length === 1 ? orderList[0] : DUMMY_ORDER_LIST[0], + }) + }), + + http.get('*/api/v1/orders/:orderId/status', ({ request, params }) => { + const orderId = params.orderId as string + + const getNextStatus = (currentStatus: string): string => { + switch (currentStatus) { + case '신규': + return '진행중' + case '진행중': + return '완료' + case '완료': + case '취소': + return currentStatus + default: + return currentStatus + } + } + return HttpResponse.json({ + status: 200, + message: 'success', + data: getNextStatus(orderId), + }) + }), // Get Menu Options http.get('*/api/v1/stores/:id/menus/:menuId/options', async ({ request }) => { return passthrough() diff --git a/src/mocks/handlers/orders.ts b/src/mocks/handlers/orders.ts new file mode 100644 index 00000000..0c60cfcb --- /dev/null +++ b/src/mocks/handlers/orders.ts @@ -0,0 +1,84 @@ +import { OrdersDetail } from '@/api/useGetOrdersDetail' + +export const DUMMY_ORDER_LIST: OrdersDetail[] = [ + { + orderId: 'REFUSE', + status: { code: 'REFUSE', desc: '주문거절' }, + orderTime: '2025-02-25T12:30:00Z', + storeName: '맛있는 식당', + tel: '010-1234-5678', + roadAddress: '서울특별시 강남구 테헤란로 123', + jibunAddress: '서울특별시 강남구 역삼동 456-7', + detailAddress: '3층 305호', + excludingSpoonAndFork: false, + requestToRider: null, + orderPrice: 15000, + deliveryPrice: 3000, + deliveryCompleteTime: null, + paymentPrice: 18000, + paymentId: 987654, + paymentType: { code: 'CARD', desc: '신용카드' }, + type: { code: 'DELIVERY', desc: '배달' }, + orderMenus: [], + }, + { + orderId: 'ONGOING', + status: { code: 'ONGOING', desc: '주문수락' }, + orderTime: '2025-02-25T12:40:00Z', + storeName: '중국집', + tel: '010-1111-2222', + roadAddress: '서울특별시 송파구 올림픽로 55', + jibunAddress: '서울특별시 송파구 잠실동 33-2', + detailAddress: '101동 202호', + excludingSpoonAndFork: false, + requestToRider: '도착하면 전화 주세요.', + orderPrice: 18000, + deliveryPrice: 2000, + deliveryCompleteTime: null, + paymentPrice: 20000, + paymentId: 765432, + paymentType: { code: 'CARD', desc: '신용카드' }, + type: { code: 'DELIVERY', desc: '배달' }, + orderMenus: [], + }, + { + orderId: 'DONE', + status: { code: 'DONE', desc: '배달완료' }, + orderTime: '2025-02-25T12:50:00Z', + storeName: '분식집', + tel: '010-5555-6666', + roadAddress: '서울특별시 성동구 왕십리로 100', + jibunAddress: '서울특별시 성동구 왕십리동 12-1', + detailAddress: '405호', + excludingSpoonAndFork: true, + requestToRider: null, + orderPrice: 12000, + deliveryPrice: 2000, + deliveryCompleteTime: '2025-02-25T13:10:00Z', + paymentPrice: 14000, + paymentId: 543210, + paymentType: { code: 'CARD', desc: '신용카드' }, + type: { code: 'DELIVERY', desc: '배달' }, + orderMenus: [], + }, + { + orderId: 'CANCELLED', + status: { code: 'CANCELLED', desc: '주문취소' }, + orderTime: '2025-02-25T12:55:00Z', + storeName: '피자집', + tel: '010-7777-8888', + roadAddress: '서울특별시 강동구 천호대로 200', + jibunAddress: '서울특별시 강동구 천호동 45-6', + detailAddress: '102호', + excludingSpoonAndFork: false, + requestToRider: null, + orderPrice: 30000, + deliveryPrice: 2500, + deliveryCompleteTime: null, + paymentPrice: 32500, + paymentId: 432109, + paymentType: { code: 'CARD', desc: '신용카드' }, + type: { code: 'DELIVERY', desc: '배달' }, + orderMenus: [], + }, +] diff --git a/src/mocks/index.ts b/src/mocks/index.ts index 5851fdce..8c361659 100644 --- a/src/mocks/index.ts +++ b/src/mocks/index.ts @@ -4,6 +4,6 @@ export async function initMsw() { server.listen() } else { const { worker } = await import('./browser') - await worker.start() + await worker.start({ onUnhandledRequest: 'bypass' }) } } From 97bc86afa491685ab7bd0207177de4d78a44354b Mon Sep 17 00:00:00 2001 From: "suhyeon.jeon" Date: Thu, 27 Feb 2025 17:42:41 +0900 Subject: [PATCH 02/24] =?UTF-8?q?=E2=9C=A8=20Feat:=20=20toss=20pay=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 10 +- eslint.config.mjs | 14 - package.json | 8 +- src/api/usePostOrderPay.ts | 7 +- src/app/pay/_components/MenuItem.tsx | 2 +- src/app/pay/_components/OrderInfo.tsx | 275 ++++++++++++------ .../pay/_components/OrderPayBottomSheet.tsx | 88 ++++++ .../pay/success/_components/PaySuccess.tsx | 48 +++ src/app/pay/success/page.tsx | 7 + src/assets/images/Pay200_Logo.png | Bin 0 -> 15484 bytes src/assets/images/Toss_Logo.png | Bin 0 -> 22820 bytes src/components/CommonLayout.tsx | 4 +- src/store/successPayment.ts | 12 + yarn.lock | 45 ++- 14 files changed, 389 insertions(+), 131 deletions(-) create mode 100644 src/app/pay/_components/OrderPayBottomSheet.tsx create mode 100644 src/app/pay/success/_components/PaySuccess.tsx create mode 100644 src/app/pay/success/page.tsx create mode 100644 src/assets/images/Pay200_Logo.png create mode 100644 src/assets/images/Toss_Logo.png create mode 100644 src/store/successPayment.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b6c597f5..d11680b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,11 +12,11 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, - "prettier.requireConfig": true, - "prettier.useEditorConfig": false, + "prettier.requireConfig": false, + "prettier.useEditorConfig": true, "javascript.format.enable": false, "typescript.format.enable": false, - "editor.formatOnSaveTimeout": 1500, + "editor.formatOnSaveTimeout": 5000, "[typescript]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -27,9 +27,9 @@ }, "[typescriptreact]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "editor.formatOnSaveMode": "modifications", + "editor.formatOnSaveMode": "file", "tailwindCSS.validate": false, "files.watcherExclude": { "**/node_modules/**": true, diff --git a/eslint.config.mjs b/eslint.config.mjs index d9132c8b..6d2a1061 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,5 @@ import js from '@eslint/js' import nextPlugin from '@next/eslint-plugin-next' -import pluginPrettier from 'eslint-plugin-prettier' import tailwind from 'eslint-plugin-tailwindcss' import ts from 'typescript-eslint' @@ -16,24 +15,11 @@ export default [ plugins: { '@typescript-eslint': ts.plugin, tailwindcss: tailwind, - prettier: pluginPrettier, '@next/next': nextPlugin, }, rules: { '@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-explicit-any': 'warn', - 'prettier/prettier': [ - 'error', - { - tabWidth: 2, - useTabs: false, - singleQuote: true, - trailingComma: 'es5', - semi: false, - endOfLine: 'lf', - printWidth: 100, - }, - ], indent: 'off', '@typescript-eslint/indent': 'off', 'tailwindcss/no-custom-classname': 'off', diff --git a/package.json b/package.json index d539ea0d..41522ae1 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@hookform/resolvers": "^3.10.0", + "@pay200/sdk": "^0.0.5", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-dialog": "^1.1.4", @@ -49,6 +50,7 @@ "@next/eslint-plugin-next": "^15.1.3", "@svgr/webpack": "^8.1.0", "@tanstack/react-query-devtools": "^5.66.3", + "@tosspayments/tosspayments-sdk": "^2.3.4", "@types/eslint-plugin-tailwindcss": "^3", "@types/node": "^20", "@types/react": "^19", @@ -58,15 +60,15 @@ "@yarnpkg/sdks": "^3.2.0", "eslint": "^9", "eslint-config-next": "15.1.3", - "eslint-config-prettier": "^9.1.0", + "eslint-config-prettier": "^10.0.2", "eslint-plugin-prettier": "^5.2.3", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-tailwindcss": "^3.17.5", "husky": "^9.1.7", "msw": "^2.7.0", "postcss": "^8", - "prettier": "^3.4.2", - "prettier-plugin-tailwindcss": "^0.6.9", + "prettier": "^3.5.2", + "prettier-plugin-tailwindcss": "^0.6.11", "tailwindcss": "^3.4.1", "typescript": "^5", "typescript-eslint": "^8.19.0" diff --git a/src/api/usePostOrderPay.ts b/src/api/usePostOrderPay.ts index 3bd70351..060034bb 100644 --- a/src/api/usePostOrderPay.ts +++ b/src/api/usePostOrderPay.ts @@ -1,6 +1,11 @@ import { api } from '@/lib/api' import { useMutation, useQueryClient } from '@tanstack/react-query' +export enum OrderPayType { + TOSS = 'TOSS', + PAY200 = 'PAY200', +} + export interface OrderPay { storeId: string // 가게ID roadAddress: string // 주문시점의 도로명주소 @@ -8,7 +13,7 @@ export interface OrderPay { detailAddress: string // 주문시점의 상세주소 excludingSpoonAndFork: boolean // 스푼과 포크 제외 여부 orderType: 'DELIVERY' | 'PACKING' // 주문타입 - paymentType: 'TOSS_PAY' | 'KAKAO_PAY' // 결제타입 + paymentType: OrderPayType // 결제타입 orderMenus: { id: string // 주문할 메뉴ID quantity: number // 주문할 메뉴 구매수량 diff --git a/src/app/pay/_components/MenuItem.tsx b/src/app/pay/_components/MenuItem.tsx index da379e19..928308c8 100644 --- a/src/app/pay/_components/MenuItem.tsx +++ b/src/app/pay/_components/MenuItem.tsx @@ -23,7 +23,7 @@ const MenuItem = ({ menu, onIncrease, onDecrease, onRemove }: MenuItemProps) => } return ( -
+
{menu.imageUrl ? ( diff --git a/src/app/pay/_components/OrderInfo.tsx b/src/app/pay/_components/OrderInfo.tsx index 9a84d7cf..f9abcb4a 100644 --- a/src/app/pay/_components/OrderInfo.tsx +++ b/src/app/pay/_components/OrderInfo.tsx @@ -4,7 +4,7 @@ import useDeleteCart from '@/api/useDeleteCarts' import useGetCarts from '@/api/useGetCarts' import useGetStoreDetail from '@/api/useGetStoreDetail' import usePatchCarts from '@/api/usePatchCarts' -import usePostOrderPay, { OrderPay } from '@/api/usePostOrderPay' +import usePostOrderPay, { OrderPay, OrderPayResponse, OrderPayType } from '@/api/usePostOrderPay' import usePostPayment from '@/api/usePostPayment' import MenuItem from '@/app/pay/_components/MenuItem' import Alert from '@/components/Alert' @@ -14,32 +14,42 @@ import Icon from '@/components/Icon' import Separator from '@/components/Separator' import { Checkbox } from '@/components/shadcn/checkbox' import { Label } from '@/components/shadcn/label' +import useBottomSheet from '@/hooks/useBottomSheet' +import { useToast } from '@/hooks/useToast' import { cn } from '@/lib/utils' import { modalStore } from '@/store/modal' +import { successPaymentStore } from '@/store/successPayment' import memberStore from '@/store/user' import { ROUTE_PATHS } from '@/utils/routes' +import { pay200SDK } from '@pay200/sdk' import { useQueryClient } from '@tanstack/react-query' +import { ANONYMOUS, loadTossPayments } from '@tosspayments/tosspayments-sdk' import { useRouter } from 'next/navigation' import { useEffect, useMemo, useState } from 'react' +import OrderPayBottomSheet from './OrderPayBottomSheet' const OrderInfo = () => { const queryClient = useQueryClient() const router = useRouter() const { carts, resetCarts } = useGetCarts() - const [cartsState, setCartsState] = useState(carts) const { mutate: deleteCarts } = useDeleteCart() const { mutate: updateCarts } = usePatchCarts() + const { storeDetail } = useGetStoreDetail(carts?.storeId || null) + const { mutate: orderPay, data: orderResponse } = usePostOrderPay() + const { mutate: payment } = usePostPayment() - const { storeDetail } = useGetStoreDetail(Number(carts?.storeId) || null) - const { member } = memberStore() - const { showModal, hideModal, modals } = modalStore() - + const [cartsState, setCartsState] = useState(carts) const [isExcludingSpoon, setIsExcludingSpoon] = useState(false) - const [deliveryPrice, setDeliveryPrice] = useState(0) + const [paymentType, setPaymentType] = useState(null) + const [deliveryPrice] = useState(0) - const { mutate: orderPay, isSuccess, data: orderResponse } = usePostOrderPay() - const { mutate: payment, isSuccess: paymentSuccess } = usePostPayment() + const { member } = memberStore() + const { showModal } = modalStore() + const { payments, setPayments } = successPaymentStore() + + const { BottomSheet, hide } = useBottomSheet() + const { toast } = useToast() const handleEmptyCart = () => { if (!cartsState) return @@ -52,27 +62,6 @@ const OrderInfo = () => { ) } const handleIncreaseQuantity = (cartId: number) => { - // TODO: 테스트용 -> 배포 시 아래걸로 바꾸기 - // const updateCartsState = (newQuantity: number) => { - // setCartsState((prev) => { - // if (!prev) return - // return { - // storeId: prev.storeId, - // orderMenus: prev.orderMenus.map(menu => { - // if (menu.menuId !== menuId) return menu - - // const unitPrice = menu.totalPrice / menu.quantity - // console.log(unitPrice, menu.quantity) - // return { - // ...menu, - // quantity: menu.quantity + 1, - // totalPrice: Math.round(unitPrice * (menu.quantity + 1)) - // } - // }) - // } - // }) - // } - const updateCartsState = (newQuantity: number) => { setCartsState((prev) => { if (!prev) return @@ -107,26 +96,6 @@ const OrderInfo = () => { } const handleDecreaseQuantity = (cartId: number) => { - // TODO: 테스트용 -> 배포 시 아래걸로 바꾸기 - // const updateCartsState = (newQuantity: number) => { - // setCartsState((prev) => { - // if (!prev) return - // return { - // storeId: prev.storeId, - // orderMenus: prev.orderMenus.map(menu => { - // if (menu.menuId !== menuId) return menu - - // const unitPrice = menu.totalPrice / menu.quantity - // console.log(unitPrice, menu.quantity) - // return { - // ...menu, - // quantity: menu.quantity - 1, - // totalPrice: Math.round(unitPrice * (menu.quantity - 1)) - // } - // }) - // } - // }) - // } const updateCartsState = (newQuantity: number) => { setCartsState((prev) => { if (!prev) return @@ -172,7 +141,7 @@ const OrderInfo = () => { deleteCarts({ cartIds: [cartId] }, { onSuccess: updateCartsState }) } - const handleOrderPay = () => { + const handleOrderPay = async () => { if (!cartsState || !member) { showModal({ content: ( @@ -186,6 +155,14 @@ const OrderInfo = () => { return } + if (!paymentType) { + toast({ + description: '결제수단을 선택해주세요.', + position: 'center', + }) + return + } + const orderData: OrderPay = { storeId: cartsState.storeId, roadAddress: member.roadAddress || '', @@ -193,11 +170,11 @@ const OrderInfo = () => { detailAddress: member.detailAddress || '', excludingSpoonAndFork: isExcludingSpoon, orderType: 'DELIVERY', + // paymentType, paymentType: 'TOSS_PAY', orderMenus: cartsState.orderMenus.map((item) => { return { id: item.menuId, - quantity: item.quantity, orderMenuOptionGroups: item.orderMenuOptionGroups.map((group) => ({ id: group.id, @@ -210,7 +187,6 @@ const OrderInfo = () => { orderPay(orderData) } - const totalMenuPrice = useMemo(() => { if (!cartsState) return 0 return Object.values(cartsState.orderMenus).reduce((acc, menu) => { @@ -218,49 +194,152 @@ const OrderInfo = () => { }, 0) }, [cartsState]) + const isUnderMinOrder = useMemo(() => { + if (!storeDetail) return true + return totalMenuPrice < storeDetail.minimumOrderAmount + }, [storeDetail, cartsState]) + + const handleSelectPaymentType = (type: OrderPayType) => { + setPaymentType(type) + hide() + } + + const handleSelectOrderPay = () => { + BottomSheet({ + title: '결제 수단', + content: ( + + ), + }) + } + + async function requestPayment(orderResponse: OrderPayResponse) { + if (!process.env.NEXT_PUBLIC_TOSS_PAY_KEY || !process.env.NEXT_PUBLIC_PAY200_KEY) { + toast({ + description: '결제 수단 설정에 문제가 있습니다.', + position: 'center', + }) + return + } + + if (paymentType === OrderPayType.PAY200) { + // SDK 초기화 + const requestPayment = pay200SDK({ + apiKey: process.env.NEXT_PUBLIC_PAY200_KEY, + }) + + try { + await requestPayment({ + orderId: orderResponse.orderId, + amount: orderResponse.totalPrice, + orderName: '개발의 민족 주문', + successUrl: `${window.location.origin}/pay/success`, + }) + } catch (error) { + console.error('결제 중 오류가 발생했습니다:', error) + } + } else if (paymentType === OrderPayType.TOSS) { + try { + const tossPayments = await loadTossPayments(process.env.NEXT_PUBLIC_TOSS_PAY_KEY) + + const payment = tossPayments.payment({ customerKey: ANONYMOUS }) + + await payment.requestPayment({ + method: 'CARD', // 카드 및 간편결제 + amount: { + currency: 'KRW', + value: orderResponse.totalPrice, + }, + orderId: orderResponse.orderId, // 고유 주문번호 + orderName: '개발의 민족 주문', + successUrl: window.location.origin + '/pay/success', // 결제 요청이 성공하면 리다이렉트되는 URL + failUrl: window.location.origin + '/pay/fail', // 결제 요청이 실패하면 리다이렉트되는 URL + // 가상계좌 안내, 퀵계좌이체 휴대폰 번호 자동 완성에 사용되는 값입니다. 필요하다면 주석을 해제해 주세요. + // customerMobilePhone: "01012341234", + card: { + useEscrow: false, + flowMode: 'DIRECT', + cardCompany: 'TOSSBANK', + useCardPoint: false, + useAppCardOnly: false, + }, + }) + } catch (error) { + console.log(error) + } + } + } + useEffect(() => { return () => { resetCarts() } }, []) + useEffect(() => { setCartsState(carts) }, [carts]) useEffect(() => { - if (orderResponse) { - payment({ - orderId: orderResponse.orderId, - paymentKey: '', - amount: orderResponse.totalPrice, - }) + // payments가 있으면 결제 승인 상태 + if (payments) { + payment( + { + orderId: payments.orderId, + paymentKey: payments.paymentKey, + amount: payments.amount, + }, + { + onSuccess: () => { + showModal({ + content: ( + { + router.replace(ROUTE_PATHS.HOME) + }} + confirmText="주문 상세" + onConfirmClick={() => { + router.replace(`${ROUTE_PATHS.ORDERS_DETAIL}/${payments.orderId}`) + }} + /> + ), + }) + }, + onError: () => { + showModal({ + content: ( + {}} + /> + ), + }) + }, + onSettled: () => { + setPayments(null) + }, + } + ) } - }, [orderResponse]) + }, [payments]) useEffect(() => { - if (paymentSuccess) { - queryClient.invalidateQueries({ queryKey: ['orders'] }) - - showModal({ - content: ( - { - handleEmptyCart() - router.push(`${ROUTE_PATHS.ORDERS_DETAIL}/${orderResponse?.orderId}`) - }} - cancelText="홈으로" - onCancelClick={() => { - handleEmptyCart() - router.push(ROUTE_PATHS.HOME) - }} - /> - ), - }) + if (orderResponse) { + requestPayment(orderResponse) + // payment({ + // orderId: orderResponse.orderId, + // paymentKey: '', + // amount: orderResponse.totalPrice, + // }) } - }, [paymentSuccess]) + }, [orderResponse]) useEffect(() => { if (cartsState && cartsState.orderMenus.length === 0) { @@ -269,11 +348,6 @@ const OrderInfo = () => { } }, [cartsState]) - const isUnderMinOrder = useMemo(() => { - if (!storeDetail) return true - return totalMenuPrice < storeDetail.minimumOrderAmount - }, [storeDetail, cartsState]) - if (!cartsState || !storeDetail) { return (
@@ -290,7 +364,7 @@ const OrderInfo = () => { } return ( -
+
@@ -314,7 +388,7 @@ const OrderInfo = () => {
{member?.jibunAddress}
-
+
router.push(`${ROUTE_PATHS.STORE_DETAIL}/${storeDetail.id}`)} @@ -329,7 +403,7 @@ const OrderInfo = () => {
-
+
{cartsState.orderMenus.map((menu, index) => ( { ))}
-
+
{
-
+
가게 요청사항
{/* */}
@@ -377,11 +451,22 @@ const OrderInfo = () => {
-
+
결제수단
-
-
결제수단을 선택해주세요
+
+
+ {!paymentType ? ( + '결제수단을 선택해주세요' + ) : ( + + {paymentType === OrderPayType.TOSS ? '토스페이' : 'PAY200'} + + )} +
diff --git a/src/app/pay/_components/OrderPayBottomSheet.tsx b/src/app/pay/_components/OrderPayBottomSheet.tsx new file mode 100644 index 00000000..ff85fb55 --- /dev/null +++ b/src/app/pay/_components/OrderPayBottomSheet.tsx @@ -0,0 +1,88 @@ +'use client' + +import { OrderPayType } from '@/api/usePostOrderPay' +import PayLogo from '@/assets/images/Pay200_logo.png' +import Toss from '@/assets/images/Toss_Logo.png' +import Separator from '@/components/Separator' +import { cn } from '@/lib/utils' +import Image from 'next/image' +import { useState } from 'react' +interface OrderPayBottomSheetProps { + currentPaymentType: OrderPayType | null + onSelectPaymentType: (type: OrderPayType) => void +} + +const OrderPayBottomSheet = ({ + currentPaymentType, + onSelectPaymentType, +}: OrderPayBottomSheetProps) => { + const [paymentType, setPaymentType] = useState( + currentPaymentType || OrderPayType.PAY200 + ) + const handleSelectPaymentType = (type: OrderPayType) => { + setPaymentType(type) + onSelectPaymentType(type) + } + return ( +
+
+ + +
+ + + +
+
+

결제 안내

+

+ • 실제로 결제가 이루어지지 않는 테스트 페이지입니다. +

+

• Pay200로 결제 시 할인 혜택을 받을 수 있습니다.

+
+
+
+ ) +} + +export default OrderPayBottomSheet + +const PayButton = ({ + type, + paymentType, + onSelectPaymentType, +}: { + type: OrderPayType + paymentType: OrderPayType + onSelectPaymentType: (type: OrderPayType) => void +}) => { + return ( +
+ +
+ ) +} diff --git a/src/app/pay/success/_components/PaySuccess.tsx b/src/app/pay/success/_components/PaySuccess.tsx new file mode 100644 index 00000000..f0fc5a25 --- /dev/null +++ b/src/app/pay/success/_components/PaySuccess.tsx @@ -0,0 +1,48 @@ +'use client' + +import Alert from '@/components/Alert' +import Icon from '@/components/Icon' +import { modalStore } from '@/store/modal' +import { successPaymentStore } from '@/store/successPayment' +import { ROUTE_PATHS } from '@/utils/routes' +import { useRouter } from 'next/navigation' +import { useEffect } from 'react' + +const PaySuccess = () => { + const params = new URLSearchParams(location.search) + const orderId = params.get('orderId') + const paymentKey = params.get('paymentKey') + const amount = params.get('amount') + + const { showModal } = modalStore() + const router = useRouter() + const { setPayments } = successPaymentStore() + + useEffect(() => { + if (orderId && paymentKey && amount) { + setPayments({ orderId, paymentKey, amount: Number(amount) }) + router.replace(ROUTE_PATHS.PAY) + } else { + showModal({ + content: ( + { + router.push(ROUTE_PATHS.PAY) + }} + /> + ), + }) + } + }, []) + + return ( +
+ +

결제가 진행중입니다...

+
+ ) +} + +export default PaySuccess diff --git a/src/app/pay/success/page.tsx b/src/app/pay/success/page.tsx new file mode 100644 index 00000000..a87f9ca4 --- /dev/null +++ b/src/app/pay/success/page.tsx @@ -0,0 +1,7 @@ +import PaySuccess from './_components/PaySuccess' + +const PaySuccessTossPage = () => { + return +} + +export default PaySuccessTossPage diff --git a/src/assets/images/Pay200_Logo.png b/src/assets/images/Pay200_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..12389e9432fc225c24142a9a3f8c25e0733da6ea GIT binary patch literal 15484 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 BW;6f* literal 0 HcmV?d00001 diff --git a/src/assets/images/Toss_Logo.png b/src/assets/images/Toss_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..697043c612c60b162ea2dc5139a6c0b2238b39b1 GIT binary patch literal 22820 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 { setIsLoading(false) }, (error) => { - console.error('위치 정보 에러:', error) + console.log('위치 정보 에러:', error) setError('위치 정보를 가져오는데 실패했습니다.') } ) } } catch (error) { - console.error('위치 정보 권한 확인 에러:', error) + console.log('위치 정보 권한 확인 에러:', error) setError('위치 정보 권한을 확인하는데 실패했습니다.') } } diff --git a/src/store/successPayment.ts b/src/store/successPayment.ts new file mode 100644 index 00000000..abe5b333 --- /dev/null +++ b/src/store/successPayment.ts @@ -0,0 +1,12 @@ +import { Payment } from '@/api/usePostPayment' +import { create } from 'zustand' + +interface SuccessPaymentState { + payments: Payment | null + setPayments: (payments: Payment | null) => void +} + +export const successPaymentStore = create((set) => ({ + payments: null, + setPayments: (payments: Payment | null) => set({ payments }), +})) diff --git a/yarn.lock b/yarn.lock index 6e7cb426..5f56b594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1877,6 +1877,15 @@ __metadata: languageName: node linkType: hard +"@pay200/sdk@npm:^0.0.5": + version: 0.0.5 + resolution: "@pay200/sdk@npm:0.0.5" + dependencies: + event-source-polyfill: "npm:^1.0.31" + checksum: 10c0/d8772a94e167a16e4af17ba434ec3cc0969c1dda91c048fee1d3d24a6021eb0733ef21defb3ba5fd5bbf9304a3cc521a14e144d73ee6deb49e7442d42172650a + languageName: node + linkType: hard + "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -2720,6 +2729,13 @@ __metadata: languageName: node linkType: hard +"@tosspayments/tosspayments-sdk@npm:^2.3.4": + version: 2.3.4 + resolution: "@tosspayments/tosspayments-sdk@npm:2.3.4" + checksum: 10c0/73b5534dc6c826509ab022cf93a36c44cd459c30455a7a058a418fb10538429750511840f5cd2da1348624e9d8fddb059c6038fbf0ea35ff62e5fe16d8cd5f09 + languageName: node + linkType: hard + "@trysound/sax@npm:0.2.0": version: 0.2.0 resolution: "@trysound/sax@npm:0.2.0" @@ -4398,14 +4414,14 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^9.1.0": - version: 9.1.0 - resolution: "eslint-config-prettier@npm:9.1.0" +"eslint-config-prettier@npm:^10.0.2": + version: 10.0.2 + resolution: "eslint-config-prettier@npm:10.0.2" peerDependencies: eslint: ">=7.0.0" bin: - eslint-config-prettier: bin/cli.js - checksum: 10c0/6d332694b36bc9ac6fdb18d3ca2f6ac42afa2ad61f0493e89226950a7091e38981b66bac2b47ba39d15b73fff2cd32c78b850a9cf9eed9ca9a96bfb2f3a2f10d + eslint-config-prettier: build/bin/cli.js + checksum: 10c0/e0ef3c442661a26fc6e82acec5bb9a418c4a8f65ec8adf0983d3aaba7716d2ed448358b063cce6e3c272c847d14cb856ddf30031770c6571e2b2c3e2a439afd4 languageName: node linkType: hard @@ -4705,6 +4721,13 @@ __metadata: languageName: node linkType: hard +"event-source-polyfill@npm:^1.0.31": + version: 1.0.31 + resolution: "event-source-polyfill@npm:1.0.31" + checksum: 10c0/79966f5084796e14f9a9dec315a2ccc220dedc51ff5f2b198dc80e3cb2ae01428d39d9bf66ed679f1944be086b9f6e84ea3dc933b81b0411c07f99672135679b + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.2 resolution: "exponential-backoff@npm:3.1.2" @@ -6353,6 +6376,7 @@ __metadata: "@eslint/js": "npm:^9.17.0" "@hookform/resolvers": "npm:^3.10.0" "@next/eslint-plugin-next": "npm:^15.1.3" + "@pay200/sdk": "npm:^0.0.5" "@radix-ui/react-accordion": "npm:^1.2.2" "@radix-ui/react-checkbox": "npm:^1.1.3" "@radix-ui/react-dialog": "npm:^1.1.4" @@ -6365,6 +6389,7 @@ __metadata: "@tanstack/react-query": "npm:^5.64.1" "@tanstack/react-query-devtools": "npm:^5.66.3" "@tanstack/react-virtual": "npm:^3.13.0" + "@tosspayments/tosspayments-sdk": "npm:^2.3.4" "@types/eslint-plugin-tailwindcss": "npm:^3" "@types/node": "npm:^20" "@types/react": "npm:^19" @@ -6376,7 +6401,7 @@ __metadata: clsx: "npm:^2.1.1" eslint: "npm:^9" eslint-config-next: "npm:15.1.3" - eslint-config-prettier: "npm:^9.1.0" + eslint-config-prettier: "npm:^10.0.2" eslint-plugin-prettier: "npm:^5.2.3" eslint-plugin-react-hooks: "npm:^5.1.0" eslint-plugin-tailwindcss: "npm:^3.17.5" @@ -6387,8 +6412,8 @@ __metadata: msw: "npm:^2.7.0" next: "npm:15.1.3" postcss: "npm:^8" - prettier: "npm:^3.4.2" - prettier-plugin-tailwindcss: "npm:^0.6.9" + prettier: "npm:^3.5.2" + prettier-plugin-tailwindcss: "npm:^0.6.11" react: "npm:^19.0.0" react-daum-postcode: "npm:^3.2.0" react-dom: "npm:^19.0.0" @@ -6813,7 +6838,7 @@ __metadata: languageName: node linkType: hard -"prettier-plugin-tailwindcss@npm:^0.6.9": +"prettier-plugin-tailwindcss@npm:^0.6.11": version: 0.6.11 resolution: "prettier-plugin-tailwindcss@npm:0.6.11" peerDependencies: @@ -6871,7 +6896,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.4.2": +"prettier@npm:^3.5.2": version: 3.5.2 resolution: "prettier@npm:3.5.2" bin: From 9fa3f15671849ece82ca1f04445dea79b8667bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9A=A9=EC=84=9D?= <4up456@naver.com> Date: Thu, 27 Feb 2025 19:56:25 +0900 Subject: [PATCH 03/24] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=BD=98=EC=86=94=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/reviews/_components/ReviewEditorModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/reviews/_components/ReviewEditorModal.tsx b/src/app/reviews/_components/ReviewEditorModal.tsx index ed725a78..bbd0a5e3 100644 --- a/src/app/reviews/_components/ReviewEditorModal.tsx +++ b/src/app/reviews/_components/ReviewEditorModal.tsx @@ -144,7 +144,6 @@ const ReviewEditorModal = ({ }, } ) - console.log('🚀 data.image:', data.image) } } From 4823589d2760f4330340e2fe5eba1d843821fd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=9A=A9=EC=84=9D?= <4up456@naver.com> Date: Thu, 27 Feb 2025 20:03:49 +0900 Subject: [PATCH 04/24] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=97=90=EB=94=94=ED=84=B0=20=EB=AA=A8=EB=8B=AC=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8D=98=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../reviews/_components/ReviewEditorModal.tsx | 157 ++++++++++-------- 1 file changed, 84 insertions(+), 73 deletions(-) diff --git a/src/app/reviews/_components/ReviewEditorModal.tsx b/src/app/reviews/_components/ReviewEditorModal.tsx index bbd0a5e3..7eda8ad9 100644 --- a/src/app/reviews/_components/ReviewEditorModal.tsx +++ b/src/app/reviews/_components/ReviewEditorModal.tsx @@ -148,91 +148,102 @@ const ReviewEditorModal = ({ } return ( -
+
{storeName}
-
이 가게를 추천하시겠어요?
-
{orderSummary}
- setValue('totalScore', value)} - size={40} - /> -
- setValue('tasteScore', value)} - /> +
+
이 가게를 추천하시겠어요?
+
{orderSummary}
+ setValue('quantityScore', value)} - /> -
-
-