Carpool(React Native & Express & apollo federation & Graphql & Mariadb, Mongodb) - 17. Payment Screen

yellow_note·2022년 2월 15일
0

#1 UI 프로토 타입

위와 같이 총 3개의 화면을 하나의 네비게이션으로 묶어 화면을 작성해보도록 하겠습니다.

#2 payment screens

  • ./src/screens/payment/CarpoolInformationScreen.js
import React from 'react';
import { View } from 'react-native';
import styles from '../../common/styles/styled.screen';
import CarpoolInformationBlock from './components/CarpoolInformationBlock';

const CarpoolInformationScreen = () => {
    return(
        <View style={ styles.container }>
            <CarpoolInformationBlock />
        </View>
    );
};

export default CarpoolInformationScreen;
  • ./src/screens/components/CarpoolInformationBlock.js
import { useNavigation, useRoute } from '@react-navigation/native';
import Ionicons from 'react-native-vector-icons/Ionicons';
import React from 'react';
import { Text, View } from 'react-native';
import FullButton from '../../../common/FullButton';
import styles from '../styles/styled.carpool.block';

const CarpoolInformationBlock = () => {
    const navigation = useNavigation();
    const route = useRoute();
    const { carpool } = route.params;
    const toBilling = () => {
        navigation.navigate("PaymentScreen", {
            carpool
        });
    };
    
    return (
        <View style={ styles.block }>
            <View style={ styles.content_block }>
                <View style={ styles.location }>
                    <Text style={ styles.font }>
                        { carpool.start_location.description }
                    </Text>
                </View>
                <View style={ styles.location }>
                    <Ionicons name="ios-arrow-down-sharp"/>
                </View>
                <View style={ styles.location }>
                    <Text style={ styles.font }>
                        { carpool.dest_location.description }
                    </Text>
                </View>
                <View>
                    <Text>
                        { carpool.start_time }
                    </Text>
                </View>
            </View>
            <View style={ styles.button }>
                <FullButton text="결제하기" 
                            onPress={ toBilling }
                /> 
            </View>
        </View>
    )
};

export default CarpoolInformationBlock;
  • ./src/screens/payment/styles/styled.carpool.block.js
import { StyleSheet } from "react-native";
import palette from "../../../utils/palette";

const styles = StyleSheet.create({
    block: { 
        flex: 1, 
        flexDirection: 'column', 
        justifyContent: 'center', 
        alignItems: 'center' 
    },
    content_block: { 
        flex: 0.7, 
        margin: 10,
        justifyContent: 'center',
        alignItems: 'center' 
    },
    location: { 
        flex: 0.1 
    },
    button: { 
        flex: 0.3, 
        marginBottom: 10 
    },
    font: {
        fontWeight: 'bold',
        shadowOpacity: 0
    }
});

export default styles;

CarpoolInformationScreen은 예약하기 전 카풀의 출발지와 목적지, 요금 등을 확인하는 스크린입니다. 해당 스크린에서 카풀 이용을 위한 정보를 확인한 뒤 결제 페이지로 넘어가는 네비게이션 기능을 수행합니다.

  • ./src/screens/payment/PaymentScreen.js
import React from 'react';
import { View } from 'react-native';
import styles from '../../common/styles/styled.screen';
import PaymentForm from './components/PaymentForm';

const PaymentScreen = () => {
    return(
        <View style={ styles.container }>
            <PaymentForm />
        </View>
    );
};

export default PaymentScreen;
  • ./src/screens/payment/components/PaymentForm.js
import { useNavigation, useRoute } from '@react-navigation/native';
import React, { useEffect } from 'react';
import { Text, View } from 'react-native';
import { useSelector } from 'react-redux';
import FullButton from '../../../common/FullButton';
import { paymentHook } from '../../../modules/payment/payment.hooks';
import styles from '../styles/styled.payment.form';
import Toast from 'react-native-toast-message';
import Loading from '../../../common/Loading';

const PaymentForm = () => {
    const navigation = useNavigation();
    const route = useRoute();
    const { carpool } = route.params;
    const [billing, { loading }] = paymentHook.useBilling();
    const toSuccessScreen = () => {
        form.payment_name = carpool.dest_location.description;
        form.user_nickname = user.nickname;
        form.ride_info_id = carpool.ride_info_id;

        const input = { ...form };

        billing({
            variables: {
                input
            }
        }).then(response => {
            if(response.data) {
                navigation.navigate("SuccessPaymentScreen");
            }
        }).catch(error => {
            Toast.show({
                type: 'info',
                text1: error.toString()
                            .split(":")[1],
                position: 'bottom'
            });

            return;
        });
    };
    const { 
        form,
        user
    } = useSelector(({ 
        user,
        payment
    }) => ({
        form: payment.billingInput,
        user: user.user
    }));

    return (
        <View style={ styles.block }>
            {
                loading ?
                <Loading /> :
                <View style={ styles.block }>
                    <View style={ styles.content_block }>
                        <View style={ styles.row }>
                            <View style={ styles.name }>
                                <Text style={ styles.boldFont }>
                                    결제 수단
                                </Text>
                            </View>
                            <View style={ styles.label }>
                                <Text style={ styles.font }>
                                    카드
                                </Text>
                            </View>
                        </View>
                        <View style={ styles.row }>
                            <View style={ styles.name }>
                                <Text style={ styles.boldFont }>
                                    비용
                                </Text>
                            </View>
                            <View style={ styles.label }>
                                <Text>
                                    3000
                                </Text>
                            </View>
                        </View>
                        <View style={ styles.row }>
                            <View style={ styles.name }>
                                <Text style={ styles.boldFont }>
                                    결제명
                                </Text>
                            </View>
                            <View style={ styles.label }>
                                <Text>
                                    { carpool.dest_location.description }
                                </Text>
                            </View>
                        </View>
                        <View style={ styles.row }>
                            <View style={ styles.name }>
                                <Text style={ styles.boldFont }>
                                    결제자 닉네임
                                </Text>
                            </View>
                            <View style={ styles.label }>
                                <Text>
                                    { user.nickname }
                                </Text>
                            </View>
                        </View>
                    </View>
                    <View style={ styles.button }>
                        <FullButton text="결제하기" 
                                    onPress={ toSuccessScreen }
                        /> 
                    </View>
                </View>
            }
        </View>
    )
};

export default PaymentForm;
  • ./src/screens/payment/styles/styled.payment.form.js
import { StyleSheet } from "react-native";
import palette from "../../../utils/palette";

const styles = StyleSheet.create({
    block: { 
        flex: 1, 
        flexDirection: 'column', 
        justifyContent: 'center', 
        alignItems: 'center' 
    },
    row: {
        flex: 0.15,
        flexDirection: 'column',
        marginTop: 4,
        marginBottom: 4
    },
    name: {
        flex: 1,
        flexDirection: 'row'
    },
    label: {
        flex: 1,
        flexDirection: 'row'
    },
    boldFont: {
        fontWeight: 'bold',
        shadowOpacity: 0
    }
});

export default styles;

Payment Screen은 결제를 진행하는 스크린입니다. billing Mutation을 이용하여 payment-service와 통신을 진행한 후 결제를 진행합니다.

  • ./src/screens/payment/SuccessPaymentScreen.js
import React from 'react';
import { View } from 'react-native';
import styles from '../../common/styles/styled.screen';
import SuccessPaymentForm from './components/SuccessPaymentForm';

const SuccessPaymentScreen = () => {
    return (
        <View style={ styles.container }>
            <SuccessPaymentForm />
        </View>
    );
};

export default SuccessPaymentScreen;
  • ./src/screens/payment/components/SuccessPaymentForm.js
import { CommonActions, useNavigation } from '@react-navigation/native';
import React from 'react';
import { Text, View } from 'react-native';
import FullButton from '../../../common/FullButton';
import styles from '../../../common/styles/styled.form';

const SuccessPaymentForm = () => {
    const navigation = useNavigation();
    const toMainScreen = e => {
        navigation.dispatch(CommonActions.reset({
            routes: [{ name: 'bottom-nav' }]
        }));
    };

    return(
        <View style={ styles.block }>
            <View style={ styles.box }>
                <Text>
                    성공적으로 예약이 완료되었습니다!
                </Text>
                <FullButton onPress={ toMainScreen }
                            text="메인 페이지로 가기"
                />
            </View>
        </View>
    );
};

export default SuccessPaymentForm;

최종 결제가 완료된 후 결제가 성공했음을 알리는 스크린입니다.

  • ./src/navigator/HomeNavigator.js
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import HomeScreen from '../screens/home/HomeScreen';
import CarpoolDetailScreen from '../screens/home/CarpoolDetailScreen';
import DriverDetailScreen from '../screens/home/DriverDetailScreen';
import CarpoolInformationScreen from '../screens/payment/CarpoolInformationScreen';
import PaymentScreen from '../screens/payment/PaymentScreen';
import SuccessPaymentScreen from '../screens/payment/SuccessPaymentScreen';

const Stack = createStackNavigator();

const HomeNavigator = () => {
    return (
        <Stack.Navigator>
            ...
            <Stack.Screen name="CarpoolInformationScreen"
                          component={ CarpoolInformationScreen }
            />
            <Stack.Screen name="PaymentScreen"
                          component={ PaymentScreen }
            />
            <Stack.Screen name="SuccessPaymentScreen"
                          component={ SuccessPaymentScreen }
            />
        </Stack.Navigator>
    );
};

export default HomeNavigator;

홈 네비게이터에 결제 스크린들을 등록하도록 하겠습니다.

스크린이 완성되었으니 apollo 모듈을 작성하여 payment-service와의 연결을 진행하도록 하겠습니다.

  • ./src/modules/apollo/payment/queries.js
import { gql } from '@apollo/client';

export const GET_PAYMENT = gql`
    query GetPayment($payment_id: String!) {
        getPayment(payment_id: $payment_id) {
            payment_id,
            payment_type,
            cost,
            payment_name,
            user_nickname,
            user_id,
            ride_info_id,
            status
        }
    }
`;

export const GET_PAYMENTS = gql`
    query GetPayments {
        getPayments {
            payment_id,
            payment_type,
            cost,
            payment_name,
            user_nickname,
            user_id,
            ride_info_id,
            status
        }
    }
`;

payment-service에서 작성했던 결제 데이터들을 가져오는 쿼리입니다.

  • ./src/modules/apollo/mutations.js
import { gql } from "@apollo/client";

export const BILLING = gql`
    mutation Billing($input: BillingInput!) {
        billing(input: $input)
    }
`;

export const CANCEL_BILLING = gql`
    mutation CancelBilling($input: CancelInput!) {
        cancelBilling(input: $input)
    }
`;

export const RE_BILLING = gql`
    mutation ReBilling($input: ReBillingInput!) {
        reBilling(input: $input)
    }
`;

결제, 결제 취소, 재결제를 위한 뮤테이션입니다.

  • ./src/modules/apollo/payment.hooks.js
import { useLazyQuery, useMutation, useQuery } from "@apollo/client"
import { BILLING, CANCEL_BILLING, RE_BILLING } from "./mutations"
import { GET_PAYMENT, GET_PAYMENTS } from "./queries";

export const paymentHook = {
    useBilling: () => {
        return useMutation(BILLING);
    },
    useCancelBilling: () => {
        return useMutation(CANCEL_BILLING);
    },
    useReBilling: () => {
        return useMutation(RE_BILLING);
    },
    useGetPayment: payment_id => {
        return useLazyQuery(GET_PAYMENT, {
            variables: {
                payment_id
            }
        });
    },
    useGetPayments: () => {
        return useQuery(GET_PAYMENTS);
    }
};

아폴로 모듈을 작성했으니 테스트를 진행해보도록 하겠습니다

#3 테스트

{
  "_id": {
    "$oid": "61dd055b4a3b9e5e990b46fe"
  },
  "ride_info_id": "IC-eg4gj5uwi-f6scvct9r75",
  "rider_id": "cd9f524e-2609-4acc-95f6-abde4df6b479",
  "passengers": [
    "06ad51a9-1d0c-4b4a-87a4-aa672407d763"
  ],
  "start_time": "2022. 1. 11. 오후 11:14:33",
  "start_location": {
    "description": "서울특별시 용산구 이태원1동",
    "latitude": "37.5400456",
    "longitude": "126.9921017"
  },
  "dest_location": {
    "description": "서울특별시 성동구 옥수동",
    "latitude": "37.54359350000001",
    "longitude": "127.0134664"
  },
  "current_location": {
    "latitude": null,
    "longitude": null
  },
  "status": "PENDING",
  "cost": 9000,
  "car": {
    "car_name": "소나타",
    "car_number": "29허1002",
    "car_size": "중형"
  },
  "__v": 0
}

테스트 스크린과 데이터 전부 잘 나오는 모습을 볼 수 있습니다. 다음 글에서는 마이페이지를 작성하고, 미결제 부분과 결제 취소를 처리하도록 하겠습니다.

0개의 댓글