diff --git a/src/apis/Bucket.ts b/src/apis/Bucket.ts index 654f038..177d87a 100644 --- a/src/apis/Bucket.ts +++ b/src/apis/Bucket.ts @@ -63,3 +63,20 @@ export const getBuckets = async (): Promise => { return null; } }; + +export const validateBucket = async (marketId: number): Promise => { + try { + const res = await apiClient.get<{ + sameMarketProduct: boolean; + }>(`/buckets/markets/${marketId}`); + + if (!res) { + return false; + } + + return res.sameMarketProduct; + } catch (error) { + console.error('Error fetching validateBucket:', error); + return false; + } +}; diff --git a/src/apis/Login.ts b/src/apis/Login.ts index 2c34502..3c37b46 100644 --- a/src/apis/Login.ts +++ b/src/apis/Login.ts @@ -25,7 +25,7 @@ const initializeNaver = ({ }: NaverLoginInitParams) => { if (Platform.OS === 'ios') { if (!serviceUrlSchemeIOS) { - console.log('serviceUrlSchemeIOS is missing in iOS initialize.'); + console.debug('serviceUrlSchemeIOS is missing in iOS initialize.'); return; } RNNaverLogin.initialize( @@ -65,7 +65,7 @@ const signInWithNaver = async (): Promise => { if (loginResult.isSuccess && loginResult.successResponse) { const {accessToken, refreshToken, expiresAtUnixSecondString} = loginResult.successResponse; - console.log('Naver Access Token:', accessToken); + console.debug('Naver Access Token:', accessToken); // JWT 토큰 const response = await apiClient.post<{ data: { @@ -79,7 +79,7 @@ const signInWithNaver = async (): Promise => { }); if (response) { - console.log('네이버 로그인 성공:', response); + console.debug('네이버 로그인 성공:', response); const accessTokenExpiresAt = Number(expiresAtUnixSecondString) * 1000; return { @@ -91,11 +91,11 @@ const signInWithNaver = async (): Promise => { jwtToken: response.data.accessToken, }; } else { - console.log('네이버 로그인 실패'); + console.debug('네이버 로그인 실패'); return null; } } else { - console.log('네이버 로그인 실패:', loginResult.failureResponse); + console.debug('네이버 로그인 실패:', loginResult.failureResponse); return null; } } catch (error) { @@ -125,7 +125,7 @@ const signInWithKakao = async (): Promise => { }); if (response) { - console.log('카카오 로그인 성공:', response); + console.debug('카카오 로그인 성공:', response); return { //TODO: JWT 토큰으로 대체 필요 accessToken: token.accessToken, @@ -136,7 +136,7 @@ const signInWithKakao = async (): Promise => { jwtToken: response.data.accessToken, }; } else { - console.log('카카오 로그인 실패'); + console.debug('카카오 로그인 실패'); return null; } } catch (error) { diff --git a/src/apis/Order.ts b/src/apis/Order.ts index e7d8943..7e123f1 100644 --- a/src/apis/Order.ts +++ b/src/apis/Order.ts @@ -1,59 +1,9 @@ +import {BucketType} from '@/types/Bucket'; +import {Success} from '@tosspayments/widget-sdk-react-native'; +import {PaymentInfo} from '@tosspayments/widget-sdk-react-native/lib/typescript/src/models/PaymentInfo'; import axios from 'axios'; -import {CartType, OrderType} from '../types/OrderType'; - -const dummyCart: CartType = { - id: 1, - market: { - id: 1, - name: 'market1', - images: ['https://legacy.reactjs.org/logo-og.png'], - }, - products: [ - { - id: 1, - name: '김치', - image: 'https://legacy.reactjs.org/logo-og.png', - originalPrice: 10000, - discountPrice: 7000, - count: 3, - tags: [ - { - id: 1, - tagName: '추천메뉴', - }, - {id: 5, tagName: '김치류'}, - ], - }, - { - id: 2, - name: '깻잎', - image: 'https://legacy.reactjs.org/logo-og.png', - originalPrice: 5000, - discountPrice: 3000, - count: 3, - tags: [ - { - id: 2, - tagName: '깻잎류', - }, - ], - }, - { - id: 3, - name: '간장게장', - image: 'https://legacy.reactjs.org/logo-og.png', - originalPrice: 20000, - discountPrice: 17000, - count: 3, - tags: [ - { - id: 3, - tagName: '게장류', - }, - ], - }, - ], -}; +import {OrderType} from '../types/OrderType'; +import apiClient from './ApiClient'; const dummyHistoryList: OrderType[] = [ { @@ -161,6 +111,13 @@ const dummyHistoryList: OrderType[] = [ }, ]; +const randomString = (): string => Math.random().toString(36).substr(2, 16); + +const dummyPaymentInfo: PaymentInfo = { + orderId: randomString(), + orderName: '김치', +}; + // TODO: fetch order history export const getOrderHistory = async (): Promise => { try { @@ -173,7 +130,7 @@ export const getOrderHistory = async (): Promise => { return new Promise(async resolve => { await new Promise(_ => setTimeout(_, 1000)); - console.log('fetch order history'); + console.debug('fetch order history'); resolve(dummyHistoryList); }); } catch (error) { @@ -182,16 +139,34 @@ export const getOrderHistory = async (): Promise => { } }; -// TODO: fetch cart -export const getCart = async (): Promise => { +export const requestOrder = async ( + cart: BucketType, +): Promise => { try { - return new Promise(async resolve => { - await new Promise(_ => setTimeout(_, 1000)); - console.log('fetch cart'); - resolve(dummyCart); - }); + // TODO: uri 수정 + // const res = await apiClient.post('/order', cart); + apiClient.get('/utils/health'); + console.debug(cart); + const res = dummyPaymentInfo; + + return res; } catch (error) { - console.error(error); + console.debug(error); + return null; + } +}; + +export const requestOrderSuccess = async ( + success: Success, +): Promise => { + try { + // TODO: uri 수정 + // const res = await apiClient.post('/order/success', success); + console.debug(success); + const res = true; + return res; + } catch (error) { + console.debug(error); return null; } }; diff --git a/src/components/common/CartNavigatorIcon.tsx b/src/components/common/CartNavigatorIcon.tsx index 9d0198c..37530cf 100644 --- a/src/components/common/CartNavigatorIcon.tsx +++ b/src/components/common/CartNavigatorIcon.tsx @@ -7,7 +7,7 @@ const CartIcon = () => { const navigation = useNavigation>(); const handlePress = () => { - navigation.navigate('Cart'); + navigation.navigate('CartRoot'); }; return ( diff --git a/src/components/orderPage/PaymentMethod.tsx b/src/components/orderPage/PaymentMethod.tsx index ff1e3bf..7993b82 100644 --- a/src/components/orderPage/PaymentMethod.tsx +++ b/src/components/orderPage/PaymentMethod.tsx @@ -1,38 +1,8 @@ import React from 'react'; -import {View} from 'react-native'; -import {RadioButton} from 'react-native-paper'; import S from './PaymentMethod.style'; -const PaymentMethod = ({ - value, - onChange, - paymentMethodKind, -}: { - value: T; - onChange: (method: T) => void; - paymentMethodKind: { - [key in T]: string; - }; -}) => { - return ( - - 결제수단 - - onChange(newValue as T)} - value={value}> - {Object.entries(paymentMethodKind).map(([kind, label]) => ( - - ))} - - - - ); +const PaymentMethod = ({children}: {children: React.ReactNode}) => { + return {children}; }; export default PaymentMethod; diff --git a/src/navigation/CartNavigator.tsx b/src/navigation/CartNavigator.tsx index 49e9db9..71e2f73 100644 --- a/src/navigation/CartNavigator.tsx +++ b/src/navigation/CartNavigator.tsx @@ -8,7 +8,7 @@ const Stack = createStackNavigator(); const CartNavigator = () => { return ( diff --git a/src/navigation/index.tsx b/src/navigation/index.tsx index 93e3ca5..b6cdd26 100644 --- a/src/navigation/index.tsx +++ b/src/navigation/index.tsx @@ -16,8 +16,7 @@ const AppNavigator = () => { - - {/* Add more screens here */} + ); }; diff --git a/src/screens/OrderDoneScreen/OrderDoneScreen.style.tsx b/src/screens/OrderDoneScreen/OrderDoneScreen.style.tsx index 3ae5597..b20ea38 100644 --- a/src/screens/OrderDoneScreen/OrderDoneScreen.style.tsx +++ b/src/screens/OrderDoneScreen/OrderDoneScreen.style.tsx @@ -1,6 +1,11 @@ import styled from '@emotion/native'; import {Card} from 'react-native-paper'; +const OrderDoneContainer = styled.View` + display: flex; + flex-direction: column; +`; + const OrderDoneCard = styled(Card)` padding: 16px; margin: 8px; @@ -11,6 +16,41 @@ const OrderDoneCard = styled(Card)` background-color: white; `; -const S = {OrderDoneCard}; +const ProductItem = styled.View` + display: flex; + flex-direction: row; + + justify-content: space-between; +`; + +const PriceView = styled.View` + width: 100%; + + display: flex; + flex-direction: column; +`; + +const PriceItem = styled.View` + display: flex; + flex-direction: row; + + justify-content: space-between; +`; + +const PrimaryText = styled.Text` + ${({theme}) => theme.fonts.body1}; + font-weight: bold; + + padding: 8px 0; +`; + +const S = { + OrderDoneContainer, + OrderDoneCard, + ProductItem, + PriceView, + PriceItem, + PrimaryText, +}; export default S; diff --git a/src/screens/OrderDoneScreen/index.tsx b/src/screens/OrderDoneScreen/index.tsx index b9262aa..f2e7ff4 100644 --- a/src/screens/OrderDoneScreen/index.tsx +++ b/src/screens/OrderDoneScreen/index.tsx @@ -8,19 +8,47 @@ import S from './OrderDoneScreen.style'; type Props = StackScreenProps; const OrderDoneScreen = ({navigation, route}: Props) => { - const {orderId} = route.params; + const {orderId, products, originalPrice, discountPrice} = route.params; return ( - - 주문완료 - - 주문이 완료되었습니다. - {`주문번호: ${orderId}`} - + + + 주문완료 + + 주문이 완료되었습니다. + {`주문번호: ${orderId}`} + + + + 주문 상품 + {products.map(product => ( + + {product.name} + {`${product.count.toLocaleString()}개`} + + ))} + + + 결제 정보 + + + 결제 금액 + {`${discountPrice.toLocaleString()}원`} + + + 상품 금액 + {`${originalPrice.toLocaleString()}원`} + + + 할인 금액 + {`- ${(originalPrice - discountPrice).toLocaleString()}원`} + + + - + ); }; diff --git a/src/screens/PaymentScreen/PaymentPage.tsx b/src/screens/PaymentScreen/PaymentPage.tsx index 2b00e5d..5fe9659 100644 --- a/src/screens/PaymentScreen/PaymentPage.tsx +++ b/src/screens/PaymentScreen/PaymentPage.tsx @@ -11,14 +11,19 @@ import { usePaymentWidget, } from '@tosspayments/widget-sdk-react-native'; -import {CartType} from '@/types/OrderType'; +import {requestOrder, requestOrderSuccess} from '@/apis'; +import {BucketType} from '@/types/Bucket'; import {RootStackParamList} from '@/types/StackNavigationType'; import {BottomButton} from '@components/common'; -import {DatePickerCard, PaymentSummary} from '@components/orderPage'; +import { + DatePickerCard, + PaymentMethod, + PaymentSummary, +} from '@components/orderPage'; import S from './PaymentPage.style'; -type Props = {cart: CartType}; +type Props = {cart: BucketType}; const PaymentPage = ({cart}: Props) => { const navigation = useNavigation>(); @@ -43,20 +48,20 @@ const PaymentPage = ({cart}: Props) => { - <> + { paymentWidgetControl .renderPaymentMethods( 'payment-methods', - {value: 50000}, + {value: discountPrice}, { variantKey: 'DEFAULT', }, ) .then(control => { - console.log({control}); + console.debug({control}); }); }} /> @@ -72,7 +77,7 @@ const PaymentPage = ({cart}: Props) => { }); }} /> - + { return; } - paymentWidgetControl - .requestPayment?.({ - orderId: '1lB4sMvBNySuMmzDy5PPv', - orderName: '토스 티셔츠 외 2건', - }) - .then(result => { - if (result?.success) { - // 결제 성공 비즈니스 로직을 구현하세요. - // result.success에 있는 값을 서버로 전달해서 결제 승인을 호출하세요. - navigation.navigate('Detail', { - screen: 'OrderDone', - params: {orderId: 1}, - }); - } else if (result?.fail) { - // 결제 실패 비즈니스 로직을 구현하세요. - Alert.alert('결제 실패'); - } + const orderRes = await requestOrder(cart); + + if (orderRes == null) { + Alert.alert('주문 정보를 가져오지 못했습니다.'); + return; + } + + const tossPaymentRes = + await paymentWidgetControl.requestPayment?.(orderRes); + + if (tossPaymentRes == null) { + Alert.alert('결제 정보를 가져오지 못했습니다.'); + return; + } + + console.debug(tossPaymentRes); + + if (tossPaymentRes.success) { + // 결제 성공 비즈니스 로직을 구현하세요. + // result.success에 있는 값을 서버로 전달해서 결제 승인을 호출하세요. + const successRes = await requestOrderSuccess( + tossPaymentRes.success, + ); + + if (!successRes) { + Alert.alert('결제 승인을 실패했습니다.'); + return; + } + + navigation.navigate('Detail', { + screen: 'OrderDone', + params: { + orderId: tossPaymentRes.success.orderId, + products: cart.products, + originalPrice, + discountPrice, + }, }); + } else if (tossPaymentRes.fail) { + // 결제 실패 비즈니스 로직을 구현하세요. + Alert.alert('결제 실패'); + } }}> {`${discountPrice.toLocaleString()}원 결제하기`} diff --git a/src/screens/PaymentScreen/index.tsx b/src/screens/PaymentScreen/index.tsx index 8324e77..dd341ed 100644 --- a/src/screens/PaymentScreen/index.tsx +++ b/src/screens/PaymentScreen/index.tsx @@ -1,6 +1,6 @@ -import {CartType} from '@/types/OrderType'; +import {getBuckets} from '@/apis/Bucket'; +import {BucketType} from '@/types/Bucket'; import {DetailStackParamList} from '@/types/StackNavigationType'; -import {getCart} from '@/apis'; import {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect, useState} from 'react'; import EmptyCartPage from './EmptyCartPage'; @@ -9,21 +9,21 @@ import PaymentPage from './PaymentPage'; type Props = StackScreenProps; const PaymentScreen = ({navigation}: Props) => { - const [cart, setCart] = useState(null); + const [cart, setCart] = useState(null); useEffect(() => { const fetchCart = async () => { try { - const res = await getCart(); + const res = await getBuckets(); if (!res) { - console.log('error'); + console.debug('error'); setCart(null); return; } setCart(res); } catch (e) { - console.log(e); + console.debug(e); setCart(null); } }; diff --git a/src/types/StackNavigationType.ts b/src/types/StackNavigationType.ts index 52499b7..aec1f61 100644 --- a/src/types/StackNavigationType.ts +++ b/src/types/StackNavigationType.ts @@ -1,4 +1,5 @@ import {ParamListBase} from '@react-navigation/native'; +import {OrderType} from './OrderType'; type StackParamType = { screen?: keyof T; @@ -20,7 +21,12 @@ export interface RegisterStackParamList extends ParamListBase { export interface DetailStackParamList extends ParamListBase { Market: {marketId: number}; Payment: undefined; - OrderDone: {orderId: number}; + OrderDone: { + orderId: number; + products: OrderType['products']; + originalPrice: number; + discountPrice: number; + }; } export interface CartStackParamList extends ParamListBase {