[36 | 支払い処理実装] << [ホーム] >> [38 | 受注表示]
「qrmenu_server」側で作業します。
「qrmenu_server/qrmenucore/serializers.py」ファイルに記述を追加します。
記述追加 【Desktop/QRMenu/qrmenu_server/qrmenucore/serializers.py】28行目〜
from rest_framework import serializers from . import models class MenuItemSerializer(serializers.ModelSerializer): class Meta: model = models.MenuItem fields = ( 'id', 'name', 'description', 'price', 'image', 'is_available', 'place', 'category') class CategorySerializer(serializers.ModelSerializer): menu_items = MenuItemSerializer(many=True, read_only=True) class Meta: model = models.Category fields = ( 'id', 'name', 'menu_items', 'place') class PlaceDetailSerializer(serializers.ModelSerializer): categories = CategorySerializer(many=True, read_only=True) class Meta: model = models.Place fields = ( 'id', 'name', 'image', 'number_of_tables', 'categories') class PlaceSerializer(serializers.ModelSerializer): class Meta: model = models.Place fields = ('id', 'name', 'image') class OrderSerializer(serializers.ModelSerializer): class Meta: model = models.Order fields = "__all__"
「qrmenucore/views.py」ファイルを編集します。
記述編集 【Desktop/QRMenu/qrmenu_server/qrmenucore/views.py】
import json import stripe from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.conf import settings from rest_framework import generics from . import models, serializers, permissions # Create your views here. class PlaceList(generics.ListCreateAPIView): serializer_class = serializers.PlaceSerializer def get_queryset(self): return models.Place.objects.filter(owner_id=self.request.user.id) def perform_create(self, serializer): serializer.save(owner=self.request.user) class PlaceDetail(generics.RetrieveUpdateDestroyAPIView): permission_classes = [permissions.IsOwnerOrReadOnly] serializer_class = serializers.PlaceDetailSerializer queryset = models.Place.objects.all() class CategoryList(generics.CreateAPIView): permission_classes = [permissions.PlaceOwnerOrReadOnly] serializer_class = serializers.CategorySerializer class CategoryDetail(generics.UpdateAPIView, generics.DestroyAPIView): permission_classes = [permissions.PlaceOwnerOrReadOnly] serializer_class = serializers.CategorySerializer queryset = models.Category.objects.all() class MenuItemList(generics.CreateAPIView): permission_classes = [permissions.PlaceOwnerOrReadOnly] serializer_class = serializers.MenuItemSerializer class MenuItemDetail(generics.UpdateAPIView, generics.DestroyAPIView): permission_classes = [permissions.PlaceOwnerOrReadOnly] serializer_class = serializers.MenuItemSerializer queryset = models.MenuItem.objects.all() stripe.api_key = settings.STRIPE_API_SECRET_KEY @csrf_exempt def create_pyment_intent(request): try: data = json.loads(request.body) intent = stripe.PaymentIntent.create( amount = data['amount'], currency = 'jpy', payment_method = data['payment_method']['id'], off_session=True, confirm = True, ) order = models.Order.objects.create( place_id = data['place'], table = data['table'], detail = json.dumps(data['detail']), amount = data['amount'], payment_intent = intent['id'] ) return JsonResponse({ "success": True, "order": order.id, }) except Exception as e: return JsonResponse({ "success": False, "error": str(e), }) class OrderList(generics.ListAPIView): serializer_class = serializers.OrderSerializer def get_queryset(self): return models.Order.objects.filter(place__owner_id=self.request.user.id, place_id=self.request.GET.get('place')) class OrderDetail(generics.UpdateAPIView): permission_classes = [permissions.PlaceOwnerOrReadOnly] serializer_class = serializers.OrderSerializer queryset = models.Order.objects.all()
パスの設定をします。
「qrmenu_server/urls.py」ファイルに記述を追加します。
記述追加 【QRMenu/qrmenu_server/qrmenu_server/urls.py】23行目
from django.contrib import admin from django.urls import path, include from qrmenucore import views urlpatterns = [ path('admin/', admin.site.urls), path('auth/', include('djoser.urls')), path('auth/', include('djoser.urls.authtoken')), path('api/places/', views.PlaceList.as_view()), path('api/places/<pk>', views.PlaceDetail.as_view()), path('api/categories/', views.CategoryList.as_view()), path('api/categories/<pk>', views.CategoryDetail.as_view()), path('api/menu_items/', views.MenuItemList.as_view()), path('api/menu_items/<pk>', views.MenuItemDetail.as_view()), path('api/create_payment_intent/', views.create_pyment_intent), path('api/orders/', views.OrderList.as_view()), path('api/orders/<pk>', views.OrderDetail.as_view()), ]
次は「qrmenu_react」側で作業します。
「qrmenu_react/src/apis.js」ファイルに記述を追加します。
記述追加 【Desktop/QRMenu/qrmenu_react/src/apis.js】119行目
import { toast } from 'react-toastify'; function request(path, {data = null, token = null, method = "GET" }) { return fetch(path, { method, headers: { Authorization: token ? `Token ${token}` : "", "Content-Type": "application/json", }, body: method !=="GET" && method !== "DELETE" ? JSON.stringify(data): null, }) .then((response) => { //もし成功したら if (response.ok) { if(method === "DELETE") { return true; } //toast.success("ログイン成功"); return response.json(); } //失敗 return response.json().then((json) => { //JSONエラー if (response.status === 400) { //toast.error("氏名もしくはパスワードに間違いがあります。"); const errors = Object.keys(json).map( (k) => `${(json[k].join(" "))}` ); throw new Error(errors.join(" ")); } throw new Error(JSON.stringify(json)); }) .catch((e) => { if (e.name === "SyntaxError") { throw new Error(response.statusText); } throw new Error(e); }) }) .catch((e) => { //全エラー toast(e.message, { type: "error" }); }) } export function signIn(username, password) { return request("/auth/token/login/", { data: {username, password}, method: "POST", }) } export function register(username, password) { return request("/auth/users/", { data: {username, password}, method: "POST", }) } export function fetchPlaces(token) { return request("/api/places/", {token}); } export function addPlace(data, token) { return request("/api/places/", {data, token, method: "POST" }); } export function uploadImage(image) { const formData = new FormData(); formData.append("file", image); formData.append("upload_preset", "qrmenu_photos"); return fetch("https://api.cloudinary.com/v1_1/dov57gocw/image/upload", { method: "POST", body: formData, }).then((response) => { return response.json(); }); } export function fetchPlace(id, token) { return request(`/api/places/${id}`, { token }); } export function addCategory(data, token) { return request("/api/categories/", {data, token, method: "POST"}); } export function addMenuItems(data, token) { return request("/api/menu_items/", {data, token, method: "POST"}); } export function updateMenuItem(id, data, token) { return request(`/api/menu_items/${id}`, { data, token, method: "PATCH"}); } export function removePlace(id, token) { return request(`/api/places/${id}`, {token, method: "DELETE"}); } export function removeCategory(id, token) { return request(`/api/categories/${id}`, {token, method: "DELETE"}); } export function removeMenuItem(id, token) { return request(`/api/menu_items/${id}`, {token, method: "DELETE"}); } export function updatePlace(id, data, token) { return request(`/api/places/${id}`, { data, token, method: "PATCH"}) } export function createPaymentIntent(data, token) { return request("/api/create_payment_intent/", {data, token, method: "POST"}); } export function fetchOrders(placeId, token) { return request(`/api/orders/?place=${placeId}`, { token }); }
「qrmenu_react/src/pages」フォルダに「Orders.js」ファイルを新規作成します。
作成した「src/pages/Orders.js」ファイルを以下のように編集します。
新規作成 【Desktop/QRMenu/qrmenu_react/src/pages/Orders.js】
import { IoMdArrowBack } from 'react-icons/io'; import { Row, Col, Button } from 'react-bootstrap'; import { useParams, useHistory } from 'react-router-dom'; import React, { useState, useEffect, useContext } from 'react'; import { fetchOrders } from '../apis'; import AuthContext from '../contexts/AuthContext'; import MainLayout from '../layouts/MainLayout'; const Orders = () => { const [orders, setOrders] = useState([]); const params = useParams(); const history = useHistory(); const auth = useContext(AuthContext); const onBack = () => history.push(`/places/${params.id}`); const onFetchOrders = async () => { const json = await fetchOrders(params.id, auth.token); if(json) { console.log(json); setOrders(json); } } useEffect(() => { onFetchOrders(); }, []); return ( <MainLayout> <div className="d-flex align-items-center mb-4"> <Button variant="link" onClick={onBack}> <IoMdArrowBack size={25} color="black" /> </Button> <h3 className="mb-0 ml-2 mr-2">受注リスト</h3> </div> <Row className="justify-content-center"> {orders?.map((order) => ( <Col key={order.id} lg={8}> {order.detail} </Col> ))} </Row> </MainLayout> ); } export default Orders;
「src/router/App.js」ファイルを編集します。
記述編集 【Desktop/QRMenu/qrmenu_react/src/router/App.js】
import { BrowserRouter, Switch, Route } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import React from 'react'; import { AuthProvider } from '../contexts/AuthContext'; import PrivateRoute from './PrivateRoute'; import Home from '../pages/Home'; import Login from '../pages/Login'; import Register from '../pages/Register'; import Places from '../pages/Places'; import Place from '../pages/Place'; import Menu from '../pages/Menu'; import Orders from '../pages/Orders'; function App() { return ( <AuthProvider> <BrowserRouter> <Switch> <Route exact path='/'> <Home /> </Route> <Route exact path='/login'> <Login /> </Route> <Route exact path='/menu/:id/:table'> <Menu /> </Route> <Route exact path='/register'> <Register /> </Route> <PrivateRoute exact path='/places/:id'> <Place /> </PrivateRoute> <PrivateRoute exact path='/places'> <Places /> </PrivateRoute> <PrivateRoute exact path='/places/:id/orders'> <Orders /> </PrivateRoute> </Switch> </BrowserRouter> <ToastContainer /> </AuthProvider> ) } export default App;
ブラウザを確認します。
アドレスの数字には、注文があるplace_idを入れてください。
http://localhost:3000/places/5/orders
注文のdetailが表示されます。
↓↓クリックして頂けると励みになります。
[36 | 支払い処理実装] << [ホーム] >> [38 | 受注表示]