결제 페이지를 만들어 보도록 하겠습니다. 결제 페이지는 다음의 경우 사용이 가능합니다.
1) 대관 데이터 생성 후 바로 결제하는 방법
2) 대관 데이터 생성 후 익일까지 결제를 진행하는 방법, 익일이 지나면 데이터 삭제
위의 케이스들을 고려하여 우선 UI와 리덕스 모듈을 구현한 후, payment-service와 rental-service를 수정해보도록 하겠습니다.
전에 작성한 포스트에서 PaymentScreen으로 rentalId를 넘겼으니 이 값을 받아 대관 데이터를 받아오는 모듈을 작성하도록 하겠습니다.
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`);
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를 작성해보도록 하겠습니다.
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`);
결제 요청 리덕스 모듈입니다.
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 모듈도 만들도록 하겠습니다.
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를 완성시켜보도록 하겠습니다.
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값들을 이용하여 결제를 진행할 수 있습니다.
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;
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;
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와 통신을 한 후 대관 데이터와 관련된 결제 데이터도 담아 사용자에게 보여주도록 하겠습니다.
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;
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;
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를 추가적으로 구현하도록 하겠습니다.
rental-service의 response.rental.ts vo클래스와 rental.dto.ts dto클래스에 다음의 컬럼을 추가하겠습니다.
import { IsNumber, IsObject, IsString } from "class-validator";
export class ResponseRental {
...
payment: any;
}
import { IsNumber, IsObject, IsString } from "class-validator";
export class RentalDto {
...
payment: any;
}
대관 데이터를 호출하면서 동시에 결제 데이터도 호출하기 때문에 결제 데이터를 담을 컬럼입니다.
다른 서비스에서 데이터 호출을 위해 axios 라이브러리를 사용하도록 하겠습니다.
npm install --save @nestjs/axios
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 {}
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 객체에 담습니다.
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-rental
endpoint를 작성하겠습니다.
...
@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,
});
}
}
}
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작성과 연동을 진행하도록 하겠습니다.