인프런 커뮤니티 질문&답변

limazero14님의 프로필 이미지
limazero14

작성한 질문수

[코드팩토리] [중급] Flutter 진짜 실전! 상태관리, 캐시관리, Code Generation, GoRouter, 인증로직 등 중수가 되기 위한 필수 스킬들!

결제함수 마무리하기

주문하기 클릭 시 Order 생성 안 되고 500에러가 뜹니다.

해결된 질문

작성

·

315

0

질문 내용

주문 시 계속해서 주문에 실패했다고 떠서, 무엇이 문제이지 하고 봤는데, Status 500 에러가 던져집니다.

그래서 서버 쪽 로그를 봤는데, 서버에서 이런 Exception이 던져지고 있습니다.

[Nest] 63134  - 2023. 08. 13. 오전 2:35:36   ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'restaurant')
TypeError: Cannot read properties of undefined (reading 'restaurant')
    at OrderService.postOrder (/Users/nx006/Documents/vscode/flutter-lv2-server/src/order/order.service.ts:36:8)
    at OrderController.postOrder (/Users/nx006/Documents/vscode/flutter-lv2-server/src/order/order.controller.ts:63:30)
    at /Users/nx006/Documents/vscode/flutter-lv2-server/node_modules/@nestjs/core/router/router-execution-context.js:38:29
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

 

'restaurant' 속성을 읽지 못한다는 게 무슨 말일까요? 애초에 Request Body에 Restaurant 속성 자체가 없는데, 400 에러도 아니고 왜 이런 에러가 서버 쪽에서 나는 지 모르겠습니다.

코드 전문

일단은, 현재 PostBody와 Response Body에 대한 모델입니다:

// ignore_for_file: invalid_annotation_target

/// order_model.dart
import 'package:delivery_app/common/model/model_with_id.dart';
import 'package:delivery_app/common/utils/data_utils.dart';
import 'package:delivery_app/restaurant/model/restaurant_model.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'order_model.freezed.dart';
part 'order_model.g.dart';

@freezed
class OrderProductModel with _$OrderProductModel {
  factory OrderProductModel({
    required String id,
    required String name,
    required String detail,
    @JsonKey(fromJson: DataUtils.pathToUrl) required String imgUrl,
    required int price,
  }) = _OrderProductModel;

  factory OrderProductModel.fromJson(Map<String, dynamic> json) =>
      _$OrderProductModelFromJson(json);
}

@freezed
class OrderProductAndCountModel with _$OrderProductAndCountModel {
  factory OrderProductAndCountModel({
    required OrderProductModel product,
    required int count,
  }) = _OrderProductAndCount;

  factory OrderProductAndCountModel.fromJson(Map<String, dynamic> json) =>
      _$OrderProductAndCountModelFromJson(json);
}

@freezed
class OrderModel with _$OrderModel implements IModelWithId {
  factory OrderModel({
    required String id,
    required RestaurantModel restaurant,
    required List<OrderProductAndCountModel> products,
    required int totalPrice,
    @JsonKey(fromJson: DataUtils.stringToDateTime) required DateTime createdAt,
  }) = _OrderModel;

  factory OrderModel.fromJson(Map<String, dynamic> json) =>
      _$OrderModelFromJson(json);
}
/// post_order_body.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'post_order_body.freezed.dart';
part 'post_order_body.g.dart';

@freezed
class PostOrderBody with _$PostOrderBody {
  const factory PostOrderBody({
    required String id,
    required List<PostOrderBodyProduct> products,
    required int totalPrice,
    required String createdAt,
  }) = _PostOrderBody;

  factory PostOrderBody.fromJson(Map<String, dynamic> json) =>
      _$PostOrderBodyFromJson(json);
}

@freezed
class PostOrderBodyProduct with _$PostOrderBodyProduct {
  const factory PostOrderBodyProduct({
    required String id,
    required int count,
  }) = _PostOrderBodyProduct;

  factory PostOrderBodyProduct.fromJson(Map<String, dynamic> json) =>
      _$PostOrderBodyProductFromJson(json);
}

그리고 다음은 Repository 코드입니다.

/// order_provider.dart
import 'package:delivery_app/common/const/data.dart';
import 'package:delivery_app/common/dio/dio.dart';
import 'package:delivery_app/order/model/order_model.dart';
import 'package:delivery_app/order/model/post_order_body.dart';
import 'package:dio/dio.dart' hide Headers;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retrofit/retrofit.dart';

part 'order_repository.g.dart';

final orderRepositoryProvider = Provider((ref) {
  final dio = ref.watch(dioProvider);

  return OrderRepository(dio, baseUrl: 'http://$ip/order');
});

// baseUrl : http://$ip/order
@RestApi()
abstract class OrderRepository {
  factory OrderRepository(Dio dio, {String baseUrl}) = _OrderRepository;
  @POST('/')
  @Headers({'accessToken': 'true'})
  Future<OrderModel> postOrder({
    @Body() required PostOrderBody body,
  });
}

다음은 Provider 입니다.

/// order_provider.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:delivery_app/order/model/post_order_body.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';

import 'package:delivery_app/order/model/order_model.dart';
import 'package:delivery_app/order/repository/order_repository.dart';
import 'package:delivery_app/user/provider/basket_provider.dart';

final orderProvider =
    StateNotifierProvider<OrderStateNotifier, List<OrderModel>>(
        (ref) => OrderStateNotifier(
              ref: ref,
              repository: ref.watch(orderRepositoryProvider),
            ));

class OrderStateNotifier extends StateNotifier<List<OrderModel>> {
  final Ref ref;
  final OrderRepository repository;
  OrderStateNotifier({
    required this.ref,
    required this.repository,
  }) : super([]);

  Future<bool> postOrder() async {
    const uuid = Uuid();
    final id = uuid.v4();

    final state = ref.read(basketProvider);

    try {
      await repository.postOrder(
        body: PostOrderBody(
          id: id,
          products: state
              .map((e) => PostOrderBodyProduct(
                    id: e.product.id,
                    count: e.count,
                  ))
              .toList(),
          totalPrice: state.fold<int>(
            0,
            (previousValue, element) =>
                previousValue + element.product.price * element.count,
          ),
          createdAt: DateTime.now().toString(),
        ),
      );
      return true;
    } catch (e) {
      print('error: $e');
      return false;
    }
  }
}

그리고 View 단, 특 주문하기 버튼입니다:

ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        backgroundColor: primaryColor,
                      ),
                      onPressed: basket.isEmpty
                          ? null
                          : () async {
                              final response = await ref
                                  .read(orderProvider.notifier)
                                  .postOrder();
                              if (context.mounted) {
                                if (response) {
                                  context.goNamed(OrderDonePage.routeName);
                                } else {
                                  ScaffoldMessenger.of(context).showSnackBar(
                                    const SnackBar(
                                      content: Text('주문에 실패했습니다.'),
                                    ),
                                  );
                                }
                              }
                            },
                      child: const Text('결제하기'),
                    ),

서버 쪽 코드

혹시 몰라서, 에러가 발생하는 서버 쪽 코드 역시 첨부합니다. 일단 제가 건든 코드는 없습니다.

import { Injectable } from '@nestjs/common';
import { CreateOrderDto } from './dto/create-order.dto';
import { CacheService } from '../cache/cache.service';
import { User } from '../user/entities/user.entity';
import { Order } from './entities/order.entity';
import { CoreService } from '../core/core.service';
import { PaginationDto } from '../core/dto/pagination.dto';
import { Pagination } from '../core/entity/pagination.entity';
import { OrderProduct } from './entities/order-product-entity';

@Injectable()
export class OrderService {
  constructor(
    private cacheService: CacheService,
    private coreService: CoreService,
  ) {}

  paginateOrders(user: User, paginationDto: PaginationDto): Pagination<Order> {
    const result = this.coreService.paginate(
      this.cacheService.orders,
      paginationDto,
    );

    return {
      ...result,
      data: result.data.map((item) => new Order(item)),
    };
  }

  postOrder(user: User, createOrderDto: CreateOrderDto): Order {
    const newOrder = new Order({
      id: createOrderDto.id,
      user,
      restaurant: this.cacheService.products.find(
        (x) => createOrderDto.products[0].productId === x.id,
      ).restaurant,
      products: createOrderDto.products.map((basketItem) => ({
        product: new OrderProduct(
          this.cacheService.products.find(
            (product) => basketItem.productId === product.id,
          ),
        ),
        count: basketItem.count,
      })),
      totalPrice: createOrderDto.totalPrice,
      createdAt: createOrderDto.createdAt,
    });

    this.cacheService.orders = [newOrder, ...this.cacheService.orders];

    return newOrder;
  }
}

@ApiTags('order')
@ApiExtraModels(PaginationDto, Order)
@Controller('order')
export class OrderController {
  constructor(private readonly orderService: OrderService) {}

  @UseGuards(AccessTokenGuard)
  @ApiOperation({
    summary: '주문 Pagination',
  })
  @ApiPaginatedOkResponseDecorator(Order, {
    description: 'Pagination 결과',
  })
  @Get()
  paginateOrder(
    @Request() req,
    @Query() paginationDto: PaginationDto,
  ): Pagination<Order> {
    return this.orderService.paginateOrders(req.user, paginationDto);
  }

  @UseGuards(AccessTokenGuard)
  @Post()
  @ApiOperation({
    summary: '주문 생성하기',
  })
  @ApiBody({
    type: CreateOrderDto,
  })
  @ApiOkResponse({
    status: 201,
    type: Order,
  })
  postOrder(@Request() req, @Body() body: CreateOrderDto): Order {
    return this.orderService.postOrder(req.user, body);
  }
}

 

답변 1

1

코드팩토리님의 프로필 이미지
코드팩토리
지식공유자

안녕하세요!

서버 최신 버전으로 다시 다운 받아서 테스트 해보시고 그래도 안되면 저희 카카오 채널로 오셔서 원격 신청 부탁드립니다!

https://links.codefactory.ai

감사합니다!

limazero14님의 프로필 이미지
limazero14

작성한 질문수

질문하기