LifeSports Application(ReactNative & Nest.js) - 18. payment-service, UI

yellow_note·2021년 10월 19일
0

#1 결제 페이지

결제 페이지를 만들어 보도록 하겠습니다. 결제 페이지는 다음의 경우 사용이 가능합니다.

1) 대관 데이터 생성 후 바로 결제하는 방법

2) 대관 데이터 생성 후 익일까지 결제를 진행하는 방법, 익일이 지나면 데이터 삭제

위의 케이스들을 고려하여 우선 UI와 리덕스 모듈을 구현한 후, payment-service와 rental-service를 수정해보도록 하겠습니다.

#2 리덕스 모듈

전에 작성한 포스트에서 PaymentScreen으로 rentalId를 넘겼으니 이 값을 받아 대관 데이터를 받아오는 모듈을 작성하도록 하겠습니다.

  • ./src.lib/api/rental.js
import client from './client';

export const rental = ({ 
    price,
    borrower,
    tel,
    userId,
    date,
    time,
    mapId,
    mapName
}) => client.post('http://10.0.2.2:8000/rental-service/rental', {
    price,
    borrower,
    tel,
    userId,
    date,
    time,
    mapId,
    mapName
});

export const getRental = rentalId => client.get(`http://10.0.2.2:8000/rental-service/${rentalId}/rental`);

export const getRentals = userId => client.get(`http://10.0.2.2:8000/rental-service/${userId}/rentals`);
  • ./src/modules/rentals.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as rentalAPI from '../lib/api/rental';
import { takeLatest } from "@redux-saga/core/effects";

const INITIALIZE = 'rental/INITIALIZE';

const [
    GET_RENTAL,
    GET_RENTAL_SUCCESS,
    GET_RENTAL_FAILURE,
] = createRequestActionTypes('rental/GET_RENTAL');

const [
    GET_RENTALS,
    GET_RENTALS_SUCCESS,
    GET_RENTALS_FAILURE
] = createRequestActionTypes('rental/GET_RENTALS');

export const getRental = createAction(GET_RENTAL, rentalId => rentalId);

export const getRentals = createAction(GET_RENTALS, userId => userId);

const getRentalSaga = createRequestSaga(GET_RENTAL, rentalAPI.getRental);

const getRentalsSaga = createRequestSaga(GET_RENTALS, rentalAPI.getRentals);

export function* rentalsSaga() {
    yield takeLatest(GET_RENTAL, getRentalSaga);
    yield takeLatest(GET_RENTALS, getRentalsSaga);
}

const initialState = {
    rental: null,
    rentals: null,
    error: null,
};

const rentals = handleActions(
    {
        [INITIALIZE]: (state => initialState),
        [GET_RENTAL_SUCCESS]: (state, { payload: rental }) => ({
            ...state,
            rental
        }),
        [GET_RENTAL_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [GET_RENTALS_SUCCESS]: (state, { payload: rentals }) => ({
            ...state,
            rentals,
        }),
        [GET_RENTALS_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error,
        }),
    },
    initialState,
);

export default rentals;

rentalId를 매개로 하나의 대관 데이터를 불러오고, userId를 매개로 user가 대관한 데이터를 불러오는 모듈입니다. payment 모듈을 마저 완성하고 UI를 작성해보도록 하겠습니다.

  • ./src/lib/api/payment.js
import client from './client';

export const requestPayment = ({
    paymentName,
    payer,
    rentalId,
    price
}) => client.post('http://10.0.2.2:8000/payment-service/payment', {
    paymentName,
    payer,
    rentalId,
    price
});

export const getPayments = payer => client.get(`http://10.0.2.2:8000/payment-service/${payer}/payments`);

export const getPayment = paymentId => client.get(`http://10.0.2.2:8000/payment-service/${paymentId}/payment`);

결제 요청 리덕스 모듈입니다.

  • ./src/modules/payment.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as paymentAPI from '../lib/api/payment';
import { takeLatest } from "@redux-saga/core/effects";

const CHANGE_FIELD = 'payment/CHANGE_FIELD';
const INITIAILIZE = 'payment/INITIALIZE';
const [
    REQUEST_PAYMENT,
    REQUEST_PAYMENT_SUCCESS,
    REQUEST_PAYMENT_FAILURE,
] = createRequestActionTypes('payment/REQUEST_PAYMENT');

export const changeField = createAction(CHANGE_FIELD, ({
    key,
    value
}) => ({
    key,
    value
}));

export const initialize = createAction(INITIAILIZE);

export const requestPayment = createAction(REQUEST_PAYMENT, ({
    paymentName,
    payer,
    rentalId,
    price
}) => ({
    paymentName,
    payer,
    rentalId,
    price
}));

const requestPaymentSaga = createRequestSaga(REQUEST_PAYMENT, paymentAPI.requestPayment);

export function* paymentSaga() {
    yield takeLatest(REQUEST_PAYMENT, requestPaymentSaga);
}

const intialState = {
    paymentName: null,
    payer: null,
    rentalId: null,
    price: null,
    payment: null,
    paymentError: null,
};

const payment = handleActions(
    {
        [INITIAILIZE]: state => intialState,
        [CHANGE_FIELD]: (state, { payload: { key, value }}) => ({
            ...state,
            [key]: value
        }),
        [REQUEST_PAYMENT_SUCCESS]: (state, { payload: payment }) => ({
            ...state,
            payment
        }),
        [REQUEST_PAYMENT_FAILURE]: (state, { payload: paymentError }) => ({
            ...state,
            paymentError
        }),
    },
    intialState,
);

export default payment;

결제 데이터를 가져오기 위한 payments 모듈도 만들도록 하겠습니다.

  • ./src/modules/payments.js
import { createAction, handleActions } from "redux-actions";
import createRequestSaga, { createRequestActionTypes } from "../lib/createRequestSaga";
import * as paymentAPI from '../lib/api/payment';
import { takeLatest } from "@redux-saga/core/effects";

const INITIALIZE = 'payment/INITIALIZE';

const [
    GET_PAYMENT,
    GET_PAYMENT_SUCCESS,
    GET_PAYMENT_FAILURE,
] = createRequestActionTypes('payment/GET_PAYMENT');

const [
    GET_PAYMENTS,
    GET_PAYMENTS_SUCCESS,
    GET_PAYMENTS_FAILURE
] = createRequestActionTypes('payment/GET_PAYMENTS');

export const initialize = createAction(INITIALIZE);

export const getPayment = createAction(GET_PAYMENT, paymentId => paymentId);

export const getPayments = createAction(GET_PAYMENTS, payer => payer);

const getPaymentSaga = createRequestSaga(GET_PAYMENT, paymentAPI.getPayment);

const getPaymentsSaga = createRequestSaga(GET_PAYMENTS, paymentAPI.getPayments);

export function* paymentsSaga() {
    yield takeLatest(GET_PAYMENT, getPaymentSaga);
    yield takeLatest(GET_PAYMENTS, getPaymentsSaga);
}

const initialState = {
    payment: null,
    payments: null,
    error: null,
};

const payments = handleActions(
    {
        [INITIALIZE]: (state => initialState),
        [GET_PAYMENT_SUCCESS]: (state, { payload: payment }) => ({
            ...state,
            payment
        }),
        [GET_PAYMENT_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error
        }),
        [GET_PAYMENTS_SUCCESS]: (state, { payload: payments }) => ({
            ...state,
            payments,
        }),
        [GET_PAYMENTS_FAILURE]: (state, { payload: error }) => ({
            ...state,
            error,
        }),
    },
    initialState,
);

export default payments;

payment, rentals 모듈을 완성했으니 결제 페이지 UI를 완성시켜보도록 하겠습니다.

#3 UI

  • ./src/pages/payment/components/PaymentFragment.js
import React, { useEffect } from 'react';
import { useNavigation, useRoute } from '@react-navigation/native';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { changeField, initialize, requestPayment } from '../../../modules/payment';
import StyledTextInput from '../../../styles/common/StyledTextInput';
import palette from '../../../styles/palette';
import { getRental } from '../../../modules/rentals';
import Loading from '../../../styles/common/Loading';

const PaymentFragment = () => {
    const dispatch = useDispatch();
    const route = useRoute();
    const navigation = useNavigation();
    const { 
        paymentName,
        payer,
        rentalId,
        price,
        payment,
        paymentError,
        rental,
        error 
    } = useSelector(({ 
        rentals,
        payment,
    }) => ({ 
        paymentName: payment.paymentName,
        payer: payment.payer,
        rentalId: payment.rentalId,
        price: payment.price,
        payment: payment.payment,
        paymentError: payment.paymentError,
        rental: rentals.rental 
    }));
    const onPayment = () => {
        dispatch(requestPayment({
            paymentName,
            payer,
            rentalId,
            price
        }));
    };

    useEffect(() => {
        dispatch(getRental(route.params.rentalId));
    }, [dispatch, route]);

    useEffect(() => {
        dispatch(changeField({
            key: 'paymentName',
            value: rental.mapName
        }))
    }, [dispatch, rental]);

    useEffect(() => {
        dispatch(changeField({
            key: 'payer',
            value: rental.borrower
        }))
    }, [dispatch, rental]);

    useEffect(() => {
        dispatch(changeField({
            key: 'rentalId',
            value: rental.rentalId
        }))
    }, [dispatch, rental]);

    useEffect(() => {
        dispatch(changeField({
            key: 'price',
            value: rental.price
        }))
    }, [dispatch, rental]);

    useEffect(() => {
        if(payment) {
            navigation.goBack();

            dispatch(initialize())
        }

        if(paymentError) {
            // setError
        }
    }, [dispatch, payment]);

    useEffect(() => {
        if(error) {
            // setError
        }
    }, [error]);

    return(
        rental ?
        <View style={ styles.container }>
            <View style={ styles.row } >
                <Text style={ styles.label } >
                    대관 번호
                </Text>
                <StyledTextInput placeholderTextColor={ palette.black[0] }
                                value={ rental.rentalId }
                />
            </View>
            <View style={ styles.row }>
                <Text style={ styles.label }>
                    결제 내역
                </Text>
                <StyledTextInput placeholderTextColor={ palette.gray[3] }
                                value={ rental.mapName }
                />
            </View>
            <View style={ styles.row }>
                <Text style={ styles.label }>
                    결제 금액
                </Text>
                <StyledTextInput placeholderTextColor={ palette.gray[3] }
                                value={ rental.price.toString() }
                />
            </View>
            <View style={ styles.row }>
                <Text style={ styles.label } >
                    사용자명
                </Text>
                <StyledTextInput placeholderTextColor={ palette.gray[3] }
                                value={ rental.borrower }
                />
            </View>
            <View style={ styles.row } >
                <Text style={ styles.label }>
                    체육관 전화번호
                </Text>
                <StyledTextInput placeholderTextColor={ palette.gray[3] }
                                value={ rental.tel }
                />
            </View>
            <View style={ styles.row } >
                <Text style={ styles.label } >
                    대관 날짜
                </Text>
                <StyledTextInput placeholderTextColor={ palette.gray[3] }
                                value={ rental.date + "\t" + rental.time }
                />
            </View>
            <View style={ styles.button_container }>
                <TouchableOpacity style={ styles.shape }
                                onPress={ onPayment }
                >
                    <Text style={ styles.font }>
                        결제하기
                    </Text>
                </TouchableOpacity>
            </View>
        </View> : <Loading />
    );
};

const styles = StyleSheet.create({
    container: {
        flexDirection: 'column',
        width: '90%',
        height: '90%',
        backgroundColor: palette.white[0],
    },
    row: {
        flexDirection: 'row',
        alignItems: 'center',
        overflow: 'hidden'
    },
    label: {
        fontWeight: 'bold',
        padding: 10,
    },
    button_container: {
        justifyContent: 'center',
        alignItems: 'center',
        marginTop: 30
    },
    shape: {
        justifyContent: 'center',
        alignItems: 'center',
        width: '80%',
        height: 70,
        backgroundColor: palette.blue[2]
    },
    font: {
        justifyContent: 'center',
        alignItems: 'center',
        fontWeight: 'bold',
        fontSize: 20,
        color: palette.white[0]
    }
});

export default PaymentFragment;

PaymentFragment에서는 이전 페이지에서 가져온 rentalId를 매개로 하여 대관 데이터를 가져옵니다. 그리고 대관 데이터가 state에 저장이 되면 useEffect 훅을 이용하여 결제 시 필요한 state값을 저장해주죠. 그래서 이를 state값들을 이용하여 결제를 진행할 수 있습니다.

  • ./src/pages/payment/PaymentScreen.js
import React from 'react';
import { StyleSheet, View } from 'react-native';
import PaymentContent from './components/PaymentFragment';

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

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center'
    }
});

export default PaymentScreen;
  • ./src/styles/common/StyledTextInput.js
import React from 'react';
import { StyleSheet, TextInput } from 'react-native';
import palette from '../palette';

const StyledTextInput = ({
    inputAccessoryViewID,
    placeholder,
    placeholderTextColor,
    onChange,
    value
}) => {
    return(
        <TextInput style={ styles.input }
                   placeholder={ placeholder }
                   placeholderTextColor={ placeholderTextColor }
                   inputAccessoryViewID={ inputAccessoryViewID }
                   onChange={ onChange }
                   value={ value }
        />
    );
};

const styles = StyleSheet.create({
    input: {
        width: 300,
        height: 40,
        borderRadius: 4,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: palette.white[0],
        color: palette.black[0],
        fontSize: 15,
        margin: 10,
    },
});

export default StyledTextInput;
  • ./src/navigator/map/MapStackNaivigation.js
import 'react-native-gesture-handler';
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import MapScreen from '../../pages/map/MapScreen';
import DetailScreen from '../../pages/map/DetailScreen';
import PaymentScreen from '../../pages/payment/PaymentScreen';

const Stack = createStackNavigator();

const MapStackNavigation = () => {
    return(
        <Stack.Navigator>
            <Stack.Screen name="Map"
                          component={ MapScreen }
                          options={{
                              headerShown: false,
                          }}
            />
            <Stack.Screen name="Detail"
                          component={ DetailScreen }
            />
            <Stack.Screen name="Payment"
                          component={ PaymentScreen }
            />
        </Stack.Navigator>
    );
};

export default MapStackNavigation;

마이페이지에서 대관 내역 부분을 구현하도록 하겠습니다. 대관 내역의 데이터는 결제 완료된 데이터, 미결제 데이터가 존재합니다. 대관 데이터들은 TCP 레이어를 통해서 payment-service와 통신을 한 후 대관 데이터와 관련된 결제 데이터도 담아 사용자에게 보여주도록 하겠습니다.

  • ./src/pages/user/MyRentalScreen.js
import React from 'react';
import {
    StyleSheet,
    View,
    Text
} from 'react-native';
import palette from '../../styles/palette';
import MyRentalFramgment from './components/MyRentalFramgent';

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

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: palette.white[0],      
    },
});

export default MyRentalScreen;
  • ./src/pages/user/components/MyRentalFragment.js
import React, { useEffect } from 'react';
import { StyleSheet, View } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { getRentals } from '../../../modules/rentals';
import Loading from '../../../styles/common/Loading';
import palette from '../../../styles/palette';
import MyRenalCard from './MyRentalCard';

const MyRentalFramgment = () => {
    const dispatch = useDispatch();
    const { 
        userId,
        rentals
    } = useSelector(({ 
        user,
        rentals
    }) => ({
        userId: user.user.userId, 
        rentals: rentals.rentals 
    }));
    
    useEffect(() => {
        dispatch(getRentals(userId));
    }, [dispatch]);

    return(
        <View style={ styles.container }>
            {
                rentals ?
                rentals.map((item, i) => {
                    return <MyRenalCard i={ i }
                                        item={ item }
                            />
                }) : <Loading />
            }
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: palette.gray[2],
    }
});

export default MyRentalFramgment;
  • ./src/pages/user/components/MyRentalCard.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import palette from '../../../styles/palette';

const MyRenalCard = ({ item }) => {
    return(
        <View style={ styles.container }>
            <Text style={ styles.font }>
                { item.date + " " + item.time }
            </Text>
            <Text style={ styles.font }>
                { item.mapName }
            </Text>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        alignItems: 'center',
        justifyContent: 'center',
        width: 350,
        height: 50,
        marginLeft: 30,
        marginTop: 15,
        marginBottom: 5,
        borderRadius: 30,
        backgroundColor: palette.gray[3]
    },
    font: {
        fontWeight: 'bold',
        fontSize: 15
    }
});

export default MyRenalCard;

react native에서 구현할 부분은 어느 정도 완료가 되었으니 rental-service, payment-service를 추가적으로 구현하도록 하겠습니다.

#4 rental-service, payment-service

rental-service의 response.rental.ts vo클래스와 rental.dto.ts dto클래스에 다음의 컬럼을 추가하겠습니다.

  • ./src/vo/reponse.rental.ts
import { IsNumber, IsObject, IsString } from "class-validator";

export class ResponseRental {
    ...

    payment: any;
}
  • ./src/dto/rental.dto.ts
import { IsNumber, IsObject, IsString } from "class-validator";

export class RentalDto {
    ...

    payment: any;
}

대관 데이터를 호출하면서 동시에 결제 데이터도 호출하기 때문에 결제 데이터를 담을 컬럼입니다.

다른 서비스에서 데이터 호출을 위해 axios 라이브러리를 사용하도록 하겠습니다.

npm install --save @nestjs/axios
  • ./src/rental/rental.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { MongooseModule } from '@nestjs/mongoose';
import { Rental, RentalSchema } from 'src/schema/rental.schema';
import { RentalService } from './rental.service';

@Module({
  imports: [
    MongooseModule.forFeature([{
      name: Rental.name,
      schema: RentalSchema,
    }]),
    HttpModule,
  ],
  providers: [RentalService],
  exports: [RentalService],
})
export class RentalModule {}
  • ./src/app.controller.ts
import { Body, Controller, Delete, Get, HttpStatus, Param, Patch, Post } from "@nestjs/common";
import { statusConstants } from "./constants/status.constant";
import { RentalService } from "./rental/rental.service";
import { RequestRental } from "./vo/request.rental";
import { ResponseRental } from "./vo/response.rental";
import { Builder } from 'builder-pattern';
import { RentalDto } from "./dto/rental.dto";
import { EventPattern } from "@nestjs/microservices";

@Controller('rental-service')
export class AppController {
    constructor(private readonly rentalService: RentalService) {}

    @Post('rental')
    public async rental(@Body() vo: RequestRental): Promise<any> {
        try {
            const result: any = await this.rentalService.create(Builder(RentalDto).price(vo.price)
                                                                                  .borrower(vo.borrower)
                                                                                  .tel(vo.tel)
                                                                                  .userId(vo.userId)
                                                                                  .date(vo.date)
                                                                                  .time(vo.time)
                                                                                  .mapId(vo.mapId)
                                                                                  .mapName(vo.mapName)
                                                                                  .build());

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: result.message
                });
            }

            return await Object.assign({
                statusCode: HttpStatus.CREATED,
                payload: Builder(ResponseRental).rentalId(result.payload.rentalId)
                                                .price(result.payload.price)
                                                .borrower(result.payload.borrower)
                                                .tel(result.payload.tel)
                                                .userId(result.payload.userId)
                                                .date(result.payload.date)
                                                .time(result.payload.time)
                                                .mapId(result.payload.mapId)
                                                .mapName(result.payload.mapName)
                                                .status(result.payload.status)
                                                .build(),
                message: "Successfully rental"
            });
        } catch(err) {
            return await Object.assign({
                statusCode: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    } 

    @Get(':rentalId/rental')
    public async getRental(@Param('rentalId') rentalId: string) {
        try {
            const result: any = await this.rentalService.getOne(rentalId);

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: result.message
                });
            }

            return await Object.assign({
                statusCode: HttpStatus.OK,
                payload: Builder(ResponseRental).rentalId(result.payload.rentalId)
                                                .price(result.payload.price)
                                                .borrower(result.payload.borrower)
                                                .tel(result.payload.tel)
                                                .userId(result.payload.userId)
                                                .date(result.payload.date)
                                                .time(result.payload.time)
                                                .mapId(result.payload.mapId)
                                                .mapName(result.payload.mapName)
                                                .status(result.payload.status)
                                                .build(),
                message: "Get rental by rentalId"
            });
        } catch(e) {
            return await Object.assign({
                statusCode: HttpStatus.BAD_REQUEST,
                payload: null,
                message: e
            });
        }
    }

    @Get(':userId/rentals')
    public async getRentals(@Param('userId') userId: string) {
        try {
            const result: any = await this.rentalService.getRentals(userId);
            
            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: result.message
                });
            }

            const responseRentals: Array<ResponseRental> = [];

            for(const el of result.payload) {
                responseRentals.push(el);
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: responseRentals,
                message: "Get list by userId"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_REQUEST,
                payload: null,
                message: "Error message: " + err
            });
        }
    }

    @Patch(':rentalId/rental')
    public async expiredRental(@Param('rentalId') rentalId: string) {
        try {
            const result: any = await this.rentalService.expiredRental(rentalId);

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: result.message
                });
            }

            return await Object.assign({
                statusCode: HttpStatus.OK,
                payload: null,
                message: "Successfully rental"
            });
        } catch(e) {
            return await Object.assign({
                statusCode: HttpStatus.BAD_REQUEST,
                payload: null,
                message: e
            });
        }
    }

    @EventPattern('PAYMENT_RESPONSE')
    public async responsePayment(data: any): Promise<any> {
        try {
            if(data === 'FAILURE_PAYMENT') {
                const result: any = await this.rentalService.deleteRental(Builder(RentalDto).rentalId(data.rentalId)
                                                                                            .build());

                if(result.status === statusConstants.ERROR) {
                    return await Object.assign({
                        status: HttpStatus.INTERNAL_SERVER_ERROR,
                        payload: null,
                        message: "Error message: " + result.message
                    });
                }

                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + data
                });
            }

            const result: any = await this.rentalService.completeRental(Builder(RentalDto).rentalId(data.rentalId)
                                                                                          .build());

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + result.message
                });
            }

            return await Object.assign({
               status: HttpStatus.OK,
               payload: null,
               message: "Successful complete rental!" 
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.INTERNAL_SERVER_ERROR,
                payload: null,
                message: "Error message: " + err
            });
        }
    }
}

그리고 RentalService의 getRentals 메서드를 추가하겠습니다. getRentals는 대관리스트를 불러와 해당 id값에 맞는 결제 데이터를 불러와 dto 객체에 담습니다.

  • ./src/rental/service/rental.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { statusConstants } from 'src/constants/status.constant';
import { RentalDto } from 'src/dto/rental.dto';
import { Rental, RentalDocument } from 'src/schema/rental.schema';
import { Builder } from 'builder-pattern';
import { v4 as uuid } from 'uuid';
import { ResponseRental } from 'src/vo/response.rental';
import { status } from 'src/constants/rental.status';
import { HttpService } from '@nestjs/axios';
import { map, Observable } from 'rxjs';
import { AxiosResponse } from 'axios';
import { response } from 'express';

@Injectable()
export class RentalService {
    constructor(
        @InjectModel(Rental.name) private rentalModel: Model<RentalDocument>,
        private httpService: HttpService    
    ) {}

    public async create(dto: RentalDto): Promise<any> {
        try {
            const entity: any = await new this.rentalModel(Builder(Rental).rentalId(uuid())
                                                                          .price(dto.price)
                                                                          .borrower(dto.borrower)
                                                                          .tel(dto.tel)
                                                                          .userId(dto.userId)
                                                                          .date(dto.date)
                                                                          .time(dto.time)
                                                                          .mapId(dto.mapId)
                                                                          .mapName(dto.mapName)
                                                                          .status(status.PENDING)
                                                                          .createdAt(new Date().toDateString())
                                                                          .build())
                                                                          .save();

            if(!entity) {
                return await Object.assign({
                    status: statusConstants.ERROR,
                    payload: null,
                    message: "rental-service: database error" 
                });
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: Builder(RentalDto).rentalId(entity.rentalId)
                                           .price(entity.price)
                                           .borrower(entity.borrower)
                                           .tel(entity.tel)
                                           .userId(entity.userId)
                                           .date(entity.date)
                                           .time(entity.time)
                                           .mapId(entity.mapId)
                                           .mapName(entity.mapName)
                                           .status(entity.status)
                                           .createdAt(entity.createdAt)
                                           .build(),
                message: "Successful transaction"
            });
        } catch(err) {
            return Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: "rental-service: " + err
            });
        }
    }

    public async getOne(rentalId: string): Promise<any> {
        try {
            const result: any = await this.rentalModel.findOne({ rentalId: rentalId });

            if(!result) {
                return await Object.assign({
                    statusCode: statusConstants.ERROR,
                    payload: null,
                    message: "Not rental data"
                });
            }

            return await Object.assign({
                statusCode: statusConstants.SUCCESS,
                payload: Builder(RentalDto).rentalId(result.rentalId)
                                           .price(result.price)
                                           .borrower(result.borrower)
                                           .tel(result.tel)
                                           .userId(result.userId)
                                           .date(result.date)
                                           .time(result.time)
                                           .mapId(result.mapId)
                                           .mapName(result.mapName)
                                           .status(result.status)
                                           .createdAt(result.createdAt)
                                           .build(),
                message: "Success transcation",
            });
        } catch(err) {
            return await Object.assign({
                statusCode: statusConstants.ERROR,
                payload: null,
                message: "map-service database: " + err,
            });
        }
    }

    public async getRentals(userId: string): Promise<any> {
        try {
            const result: any = await this.rentalModel.find({ userId: userId });

            if(!result) {
                return await Object.assign({
                    statusCode: statusConstants.ERROR,
                    payload: null,
                    message: "Not rental datas"
                });
            }
            
            const dtoRentals: Array<ResponseRental> = [];

            for(const el of result) {
                const payment = await this.httpService.get(`http://localhost:8000/payment-service/${el.rentalId}/payment-from-rental`)
                                                      .toPromise();
                
                const dto = Builder(RentalDto).rentalId(el.rentalId)
                                              .price(el.price)
                                              .borrower(el.borrower)
                                              .tel(el.tel)
                                              .userId(el.userId)
                                              .date(el.date)
                                              .time(el.time)
                                              .mapId(el.mapId)
                                              .mapName(el.mapName)
                                              .status(el.status)
                                              .createdAt(el.createdAt)
                                              .payment(payment.data.payload)
                                              .build();
                
                dtoRentals.push(dto);
            }

            return await Object.assign({
                statusCode: statusConstants.SUCCESS,
                payload: dtoRentals,
                message: "Success transcation",
            });
        } catch(err) {
            return await Object.assign({
                statusCode: statusConstants.ERROR,
                payload: null,
                message: "map-service database: " + err,
            });
        }
    }

    public async expiredRental(rentalId): Promise<any> {
        try {
            const result  = await this.rentalModel.updateOne({ rentalId: rentalId }, { $set: { status: status.EXPIRED }});
            
            if(!result) {
                return await Object.assign({
                    statusCode: statusConstants.ERROR,
                    payload: null,
                    message: "Not exist data",
                });
            }

            return await Object.assign({
                statusCode: statusConstants.SUCCESS,
                payload: null,
                message: "Success delete"
            });
        } catch(err) {
            return await Object.assign({
                statusCode: statusConstants.ERROR,
                payload: null,
                message: "rental-service database: " + err,
            });
        }
    }

    public async deleteRental(dto: RentalDto): Promise<any> {
        try {
            const result  = await this.rentalModel.deleteOne({ rentalId: dto.rentalId });
            
            if(!result) {
                return await Object.assign({
                    statusCode: statusConstants.ERROR,
                    payload: null,
                    message: "Not exist data",
                });
            }

            return await Object.assign({
                statusCode: statusConstants.SUCCESS,
                payload: null,
                message: "Success delete"
            });
        } catch(err) {
            return await Object.assign({
                statusCode: statusConstants.ERROR,
                payload: null,
                message: "rental-service database: " + err,
            });
        }
    }

    public async completeRental(dto: RentalDto): Promise<any> {
        try {
            const result  = await this.rentalModel.updateOne({ rentalId: dto.rentalId }, { $set: { status: status.BEING }});
            
            if(!result) {
                return await Object.assign({
                    statusCode: statusConstants.ERROR,
                    payload: null,
                    message: "Not exist data",
                });
            }

            return await Object.assign({
                statusCode: statusConstants.SUCCESS,
                payload: null,
                message: "Success update"
            });
        } catch(err) {
            return await Object.assign({
                statusCode: statusConstants.ERROR,
                payload: null,
                message: "rental-service database: " + err,
            });
        }
    }
}

payment-service에서 getRentals메서드를 위한 http://localhost:8000/payment-service/${el.rentalId}/payment-from-rentalendpoint를 작성하겠습니다.

  • ./src/app.controller.ts
...

@Controller('payment-service')
export class AppController {
    ...

    @Get(':rentalId/payment-from-rental')
    public async getPaymentFromRental(@Param('rentalId') rentalId: string): Promise<any> {
        try {
            const result: any = await this.paymentService.getPaymentFromRental(rentalId);

            console.log(result);

            if(result.status === statusConstants.ERROR) {
                return await Object.assign({
                    status: HttpStatus.INTERNAL_SERVER_ERROR,
                    payload: null,
                    message: "Error message: " + result.message
                });
            }

            if(!result.payload) {
                return await Object.assign({
                    status: HttpStatus.OK,
                    payload: null,
                    message: "Success get data"
                });
            }

            return await Object.assign({
                status: HttpStatus.OK,
                payload: Builder(ResponsePayment).paymentName(result.payload.paymentName)
                                                 .payer(result.payload.payer)
                                                 .rentalId(result.payload.rentalId)
                                                 .paymentId(result.payload.paymentId)
                                                 .price(result.payload.price)
                                                 .createdAt(result.payload.createdAt)
                                                 .build(),
                message: "Success get data"
            });
        } catch(err) {
            return await Object.assign({
                status: HttpStatus.BAD_GATEWAY,
                payload: null,
                message: "Error message: " + err,
            });
        }
    }
}
  • ./src/payment/payment.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectModel } from '@nestjs/mongoose';
import { Builder } from 'builder-pattern';
import { Model } from 'mongoose';
import { statusConstants } from 'src/constants/status.constants';
import { PaymentDto } from 'src/dto/payment.dto';
import { Payment, PaymentDocument } from 'src/schema/payment.schema';
import { v4 as uuid } from 'uuid';

@Injectable()
export class PaymentService {
    ...

    public async getPaymentFromRental(data: any) {
        try {
            const entity: any = await this.paymentModel.findOne({ rentalId: data });

            if(!entity) {
                return await Object.assign({
                    status: statusConstants.SUCCESS,
                    payload: null,
                    message: "Not exist data"
                });
            }

            return await Object.assign({
                status: statusConstants.SUCCESS,
                payload: Builder(PaymentDto).paymentId(entity.paymentId)
                                            .paymentName(entity.paymentName)
                                            .payer(entity.payer)
                                            .price(entity.price)
                                            .rentalId(entity.rentalId)
                                            .createdAt(entity.createdAt)
                                            .build(),
                message: "Successful transaction"
            });
        } catch(err) {
            return await Object.assign({
                status: statusConstants.ERROR,
                payload: null,
                message: err
            });
        }
    }
}

코드 작성이 완료되었습니다. payment-service의 데이터를 잘 가져오는지 테스트를 해보겠습니다.

userId bb49cad9-1d76-41b2-abad-7a271c4394ca, 78e834ff-f270-4a9e-9b30-1a44daff9469를 기준으로 하여 테스트를 진행하겠습니다.

1) bb49cad9-1d76-41b2-abad-7a271c4394ca

해당 userId의 경우 대여 상태가 BEING이고, 결제 데이터가 존재함을 알 수 있습니다. 따라서 이 경우에는 결제를 진행한 것으로 판단하고 대관 데이터 내역에서 결제 완료로 분류합니다.

2) 78e834ff-f270-4a9e-9b30-1a44daff9469

해당 userId의 경우 대여 상태가 PENDING이며, 이 경우에는 결제를 진행하지 않은 것으로 판단하고 대관 데이터 내역에서 미결제로 분류합니다.

간단하게 테스트를 진행해보았고, 다음 포스트에서는 대관 내역과 관련하여 좀 더 자세한 UI작성과 연동을 진행하도록 하겠습니다.

0개의 댓글

관련 채용 정보