7-3:注文する(注文を受け付ける)

2019/05/29

概要

カートが作成できたので、購入手続きを進めていく画面を作成します。
この章では、商品を届けるために住所などの個人情報を入力して注文する流れ実装します。
注文情報をデータベースに貯めて注文受け付け完了とします。

フォルダ階層

完成イメージ

ご購入者情報

確認

購入完了

全体の手順

手順は以下の通りです

  1. テーブルの作成
  2. 購入者情報入力画面の作成
  3. 注文情報をDBに貯める

テーブルの作成

必要なテーブル

注文を管理するためには、2つのテーブルが必要です。
1つ目は注文テーブル(orders)です。
注文者の個人情報と注文合計金額を保持します。

orders
項目 内容 オプション オプション
id ID int primary key auto_increment
name 氏名 varchar(256)
postcode 郵便番号 int
address 住所 varchar(256)
email メールアドレス varchar(256)
tel 電話番号 varchar(13)
total 合計金額 int
created_at 作成日時 datetime
updated_at 作成日時 datetime

2つ目は各注文の詳細(order_products)です。
ordersのidに紐づいた注文した商品の詳細を保持します。

order_products
項目 内容 オプション オプション
id ID int primary key auto_increment
order_id ordersのID int
product_name 商品名 varchar(64)
num 個数 int
price 単価 int

例えば、山田さんがトマト3つ、きゅうり2本を購入したとします。
すると、ordersとorder_productsテーブルには下記のようにデータが入ります。

orders
id name email postcode address tel total created_at updated_at
1 山田 yamada@yamada.com 1234567 東京都港区1-1-1 312345678 650 2019-5-10 10:00 2019-5-10 10:00
order_products
id order_id product_name num price
1 1 トマト 3 150
2 1 きゅうり 2 100

ordersテーブル作成

テーブルの構造が理解できたところで、DBにordersテーブルを作成していきます。
console

mysql> use corporate_db
Database changed
mysql> create table orders(id int primary key auto_increment,name varchar(256),email varchar(256),postcode int,address varchar(256),tel varchar(13),total int, created_at datetime,updated_at datetime);
Query OK, 0 rows affected (0.11 sec)

Query OKが表示されていればOKです。

order_productsテーブル作成

ordersテーブルができたので、order_productsを作成します。
console

mysql> create table order_products(id int primary key auto_increment, order_id int, product_name varchar(64),num int,price int);
Query OK, 0 rows affected (0.19 sec)

Query OKが表示されればOKです。

購入者情報入力画面の作成

データベースに貯める購入者情報を入力する画面を作成します。

ボタンのリンク編集

cart.phpの「購入手続きへ」ボタンと、「買い物を続ける」ボタンのリンクを編集していきます。
「購入手続きへ」は、これからpay.phpを作成する予定なのでリンク先にpay.phpを指定します。
「お買い物を続ける」は、shop.phpを指定します。
cart.php

 <div class="cart-btn">
-   <button type="button" class="btn btn-blue">購入手続きへ</button>
-   <button type="button" class="btn btn-gray">お買い物を続ける</button>
+   <button type="button" class="btn btn-blue" onclick="location.href='pay.php'">購入手続きへ</button>
+   <button type="button" class="btn btn-gray" onclick="location.href='shop.php'">お買い物を続ける</button>
 </div>

カートが空の場合は、購入手続きに進めないようにしたいです。
empty関数を利用して$produts配列の中が空っぽかどうか調べ、空っぽだったら「購入手続きへ」ボタンを押せないように設定します。
cart.php

 <div class="cart-btn">
-   <button type="button" class="btn btn-blue" onclick="location.href='pay.php'">購入手続きへ</button>
+   <button type="button" class="btn btn-blue" onclick="location.href='pay.php'" <?php if(empty($products)) echo 'disabled="disabled"'; ?>>購入手続きへ</button>
    <button type="button" class="btn btn-gray" onclick="location.href='shop.php'">お買い物を続ける</button>
  </div>

空の場合は、ボタンを押せないことを確認できたらOKです。

HTMLで画面作成

入力画面

cart.phpをコピーしてpay.phpを作成していきます。
cart.phpの頭のPHPの部分はすべて削除して、<div class="cartlist">〜</div>も削除します。
titleはご購入手続きに変更します。
wrapper-titleのh3タグをご購入者情報に変更し、<p>カート</p>も削除します。
パンくずリストは、TOP>カート>ご購入者情報 となるように設定します。
pay.php

<!DOCTYPE html>
<html>

<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-13xxxxxxxxx"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'UA-13xxxxxxxxx');
    </script>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>ご購入手続き|SQUARE, inc.</title>

    <link rel="icon" href="favicon.ico">

    <!-- css -->
    <link rel="stylesheet" href="styles.css">
    <link rel="stylesheet" href="responsive.css">

    <!-- icon -->
    <link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet">
</head>

<body>
    <header>
        <div class="container">
            <div class="header-logo">
                <h1><a href="index.php"><img src="img/square_logo.png" id="logo"></a></h1>
            </div>

            <!-- ハンバーガーメニューボタン -->
            <div class="toggle">
                <div>
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
            <div class="cart">
                <a href="cart.php"><i class="fas fa-shopping-cart"></i></a>
            </div>

            <nav class="sp-menu menu">
                <ul>
                    <li><a href="index.php#service">サービス</a></li>
                    <li><a href="shop.php">商品一覧</a></li>
                    <li><a href="index.php#news">お知らせ</a></li>
                    <li><a href="index.php#about">会社概要</a></li>
                    <li><a href="ブログのURL">ブログ</a></li>
                    <li><a href="register.html">会員登録</a></li>
                </ul>
            </nav>

            <nav class="pc-menu menu-left menu">
                <ul>
                    <li><a href="index.php#service">サービス</a></li>
                    <li><a href="shop.php">商品一覧</a></li>
                    <li><a href="index.php#news">お知らせ</a></li>
                    <li><a href="index.php#about">会社概要</a></li>
                    <li><a href="ブログのURL">ブログ</a></li>
                </ul>
            </nav>
            <nav class="pc-menu menu-right menu">
                <ul>
                    <li><a href="cart.php"><i class="fas fa-shopping-cart"></i></a></li>
                    <li><a href="register.html">会員登録</a></li>
                </ul>
            </nav>
        </div>
    </header>
    <main>
        <div class="breadcrumbs">
            <div class="container">
                <ul>
                    <li><a href="index.php">TOP</a></li>
                    <li><a href="cart.php">カート</a></li>
                    <li>ご購入者情報</li>
                </ul>
            </div>
        </div>
        <div class="wrapper last-wrapper">
            <div class="container">
                <div class="wrapper-title">
                    <h3>ご購入者情報</h3>
                </div>

            </div>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc.</p>
        </div>
    </footer>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script>
        $(function () {
            // ハンバーガーメニューの動作
            $('.toggle').click(function () {
                $("header").toggleClass('open');
                $(".sp-menu").slideToggle(500);
            });
        });
    </script>
</body>

</html>

pay.phpにアクセスしてみます。

こうなります。

中にフォームを作成していきます。
送信先は確認画面のpay_conf.php、methodはPOSTです。
テーブルで設定した情報に合わせてフォームを追加します。
pay.php

 <div class="container">
    <div class="wrapper-title">
        <h3>ご購入者情報</h3>
    </div>
+   <form class="pay-form"  action="pay_conf.php" method="POST">
+       <div class="form-group">
+           <p class="form-title">お名前 *</p>
+           <input type="text" name="name" required>
+       </div>
+       <div class="form-group">
+           <p class="form-title">Email *</p>
+           <input type="email" name="email" required>
+       </div>
+       <div class="form-group">
+           <p class="form-title">電話番号 *</p>
+           <input type="tel" name="tel" required>
+       </div>
+       <div class="form-group">
+           <p class="form-title">お届け先 *</p>
+           <label>郵便番号</label><br>
+           <input type="text" name="postcode" required>
+           <label>住所</label><br>
+           <input type="text" name="address" required>
+       </div>
+       <button type="submit" class="btn btn-blue">決済情報を入力する</button>
+   </form>
 </div>

CSSも追加します。
style.css

/* pay */
.pay-form {
    background-color: #ebeced;
    padding: 35px 30px;
}

.form-group label {
    font-size: 13px;
}

.sm-form {
    width: 10%;
}

responsive.css

/* pay */

.sm-form {
    width: 20%;
}

ブラウザで確認します。

確認画面

pay.phpをコピーして、pay_conf.phpを作成します。
冒頭にphpでデータを受け取るようにします。
pay_conf.php

 <?php
    // 値の受け取り
    $name = isset($_POST['name'])? htmlspecialchars($_POST['name'],ENT_QUOTES,'utf-8'):'';
    $email = isset($_POST['email'])? htmlspecialchars($_POST['email'],ENT_QUOTES,'utf-8'):'';
    $tel = isset($_POST['tel'])? htmlspecialchars($_POST['tel'],ENT_QUOTES,'utf-8'):'';
    $postcode = isset($_POST['postcode'])? htmlspecialchars($_POST['postcode'],ENT_QUOTES,'utf-8'):'';
    $address = isset($_POST['address'])? htmlspecialchars($_POST['address'],ENT_QUOTES,'utf-8'):'';

 ?>

受け取った値を各項目に表示します。
<form>内のinputタグを削除して、pタグで表示させます。
送り先もpay_end.phpに変更し、ボタンも「購入する」と「修正する」に変更しました。
pay_conf.php

-   <form class="pay-form"  action="pay_conf.php" method="POST">
+   <form class="pay-form"  action="pay_end.php" method="POST">
        <div class="form-group">
            <p class="form-title">お名前 *</p>
-          <input type="text" name="name" required>
+          <p><?php echo $name; ?></p>
        </div>
        <div class="form-group">
            <p class="form-title">Email *</p>
-           <input type="email" name="email" required>
+           <p><?php echo $email; ?></p>
        </div>
        <div class="form-group">
            <p class="form-title">電話番号 *</p>
-           <input type="tel" name="tel" required>
+           <p><?php echo $tel; ?></p>
        </div>
        <div class="form-group">
            <p class="form-title">お届け先 *</p>
            <label>郵便番号</label><br>
-           <input type="text" name="postcode" required>
+           <p><?php echo $postcode; ?></p>
            <label>住所</label><br>
-           <input type="text" name="address" required>
+           <p><?php echo $address; ?></p>
        </div>
-       <button class="btn btn-blue" onclick="location.href='./card.html'">決済情報を入力する</button>
+       <p>この内容で送信してよろしいですか?</p>
+       <button type="submit" class="btn btn-blue">購入する</button>
+       <button type="button" class="btn btn-gray" onclick="location.href='./pay.php'">修正する</button>
    </form>

pay_end.phpに情報を渡す必要があるのでinputタグのtype=hiddenで各項目設定します。
pay_conf.php

    <div class="form-group">
        <p class="form-title">お名前 *</p>
        <p><?php echo $name; ?></p>
+       <input type="hidden" name="name" value="<?php echo $name; ?>">
    </div>
    <div class="form-group">
        <p class="form-title">Email *</p>
        <p><?php echo $email; ?></p>
+       <input type="hidden" name="email" value="<?php echo $email; ?>">
    </div>
    <div class="form-group">
        <p class="form-title">電話番号 *</p>
        <p><?php echo $tel; ?></p>
+       <input type="hidden" name="tel" value="<?php echo $tel; ?>">
    </div>
    <div class="form-group">
        <p class="form-title">お届け先 *</p>
        <label>郵便番号</label><br>
        <p><?php echo $postcode; ?></p>
+       <input type="hidden" name="postcode" value="<?php echo $postcode; ?>">
        <label>住所</label><br>
        <p><?php echo $address; ?></p>
+       <input type="hidden" name="address" value="<?php echo $address; ?>">
    </div>

shop.phpから確認して、pay_conf.phpに値が入ってるのが確認できたらOKです。
(郵便番号は整数型なのでハイフンは入れずに入力します(今回は割愛しますが、ここでバリデーションチェックなどを行ってください))

注文情報をDBに貯める

購入完了画面に移ると同時にDBにデータを貯めるように実装していきます。

データ取得

pay_end.phpというファイルを作成します。
pay_conf.phpからのデータを受け取ります。
pay_end.php

 <?php
    // 値の受け取り
    $name = isset($_POST['name'])? htmlspecialchars($_POST['name'],ENT_QUOTES,'utf-8'):'';
    $email = isset($_POST['email'])? htmlspecialchars($_POST['email'],ENT_QUOTES,'utf-8'):'';
    $tel = isset($_POST['tel'])? htmlspecialchars($_POST['tel'],ENT_QUOTES,'utf-8'):'';
    $postcode = isset($_POST['postcode'])? htmlspecialchars($_POST['postcode'],ENT_QUOTES,'utf-8'):'';
    $address = isset($_POST['address'])? htmlspecialchars($_POST['address'],ENT_QUOTES,'utf-8'):'';

 ?>

そして、カートの情報もsessionから取得します。
合計金額はsessionには入っていないので、cart.phpで合計金額を算出した方法と同様にsessionの情報を元にforeachで回して取得します。
pay_end.php

 <?php
    // 値の受け取り
    $name = isset($_POST['name'])? htmlspecialchars($_POST['name'],ENT_QUOTES,'utf-8'):'';
    $email = isset($_POST['email'])? htmlspecialchars($_POST['email'],ENT_QUOTES,'utf-8'):'';
    $tel = isset($_POST['tel'])? htmlspecialchars($_POST['tel'],ENT_QUOTES,'utf-8'):'';
    $postcode = isset($_POST['postcode'])? htmlspecialchars($_POST['postcode'],ENT_QUOTES,'utf-8'):'';
    $address = isset($_POST['address'])? htmlspecialchars($_POST['address'],ENT_QUOTES,'utf-8'):'';

+   session_start();
+   $products = isset($_SESSION['products'])? $_SESSION['products']:[];
+   $total = 0;
+   foreach($products as $key => $product){
+      $subtotal = (int)$product['price']*(int)$product['count'];
+      $total += $subtotal;
+   }

ordersテーブルにinsert

orderts(注文)テーブルに取得したデータを入れていきます。
DBに接続して、INSERT文を記述します。
pay_end.php

    session_start();
    $products = isset($_SESSION['products'])? $_SESSION['products']:[];
    $total = 0;
    foreach($products as $key => $product){
       $subtotal = (int)$product['price']*(int)$product['count'];
       $total += $subtotal;
    }

+   //DB接続
+   try{
+       $dbh = new PDO("mysql:host=localhost;dbname=corporate_db","root","root");
+   }catch(PDOException $e){
+       var_dump($e->getMessage());
+       exit;
+   }

+   //ordersテーブル
+   $stmt1 = $dbh->prepare("INSERT INTO orders(name,email,tel,postcode,address,total,created_at,updated_at) VALUES(:name,:email,:tel,:postcode,:address,:total,now(),now())");
+   $stmt1->bindParam(':name',$name);
+   $stmt1->bindParam(':email',$email);
+   $stmt1->bindParam(':tel',$tel);
+   $stmt1->bindParam(':postcode',$postcode);
+   $stmt1->bindParam(':address',$address);
+   $stmt1->bindParam(':total',$total);
+   $stmt1->execute();

一旦ここまでで実行してみます。
カートに商品を入れて、購入手続きを進めて、pay_end.phpまで進めてみます。
真っ白な画面が表示されたらOKです。
コンソールでデータがたまっているか確認します。
console

mysql> use corporate_db
Database changed
mysql> select * from orders;
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
| id | name         | email             | postcode | address                             | tel        | total | created_at          | updated_at |
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
|  1 | 山田太郎     | yamada@yamada.com |  1234567 | 東京都千代田区丸の内1-1-1           | 0312345678 |   500 | 2019-05-29 16:36:21 | 2019-05-29 16:36:21       |
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
1 row in set (0.00 sec)

入力した情報と合計金額が正しくinsertされていればOKです。

次に、注文データに紐づく注文明細をorder_productsにinsertします。
order_productsに入れる情報には、ordersで自動採番されたidが必要なので、ordersのidを取得します。
最新でinsertされたidを取得すればいいです。
pay_end.php

    //ordersテーブル
    $stmt1 = $dbh->prepare("INSERT INTO orders(name,email,tel,postcode,address,total,created_at,updated_at) VALUES(:name,:email,:tel,:postcode,:address,:total,now(),now())");
    $stmt1->bindParam(':name',$name);
    $stmt1->bindParam(':email',$email);
    $stmt1->bindParam(':tel',$tel);
    $stmt1->bindParam(':postcode',$postcode);
    $stmt1->bindParam(':address',$address);
    $stmt1->bindParam(':total',$total);
    $stmt1->execute();

+   //ordersのid取得
+   $order_id = $dbh->lastInsertId();

productsをforeachで回してキーと値を取得しながらorder_productsにinsertしていきます。
pay_end.php

    //ordersのid取得
    $order_id = $dbh->lastInsertId();

+   //order_productsテーブル
+   foreach($products as $key => $product){
+       $stmt2 = $dbh->prepare("INSERT INTO order_products(order_id,product_name,num,price) VALUES(:order_id,:product_name,:num,:price)");
+       $stmt2->bindParam(':order_id',$order_id);
+       $stmt2->bindParam(':product_name',$key);
+       $stmt2->bindParam(':num',$product['count']);
+       $stmt2->bindParam(':price',$product['price']);
+       $stmt2->execute();
+   }

カートに商品を入れて、購入手続きを進めて、pay_end.phpまで進めてみます。
真っ白な画面が表示されたらOKです。
コンソールでデータがたまっているか確認します。
console

mysql> use corporate_db
Database changed
mysql> select * from orders;
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
| id | name         | email             | postcode | address                             | tel        | total | created_at          | updated_at |
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
|  1 | 山田太郎     | yamada@yamada.com |  1234567 | 東京都千代田区丸の内1-1-1           | 0312345678 |   500 | 2019-05-29 16:36:21 | 2019-05-29 16:36:21        |
|  2 | 田中二郎     | tanaka@tanaka.com | 12345678 | 東京都港区南青山1-1-1               | 0311112222 |   500 | 2019-05-29 16:39:29 | 2019-05-29 16:39:29       |
+----+--------------+-------------------+----------+-------------------------------------+------------+-------+---------------------+------------+
2 rows in set (0.00 sec)

ordersを実装したときに確認したレコードの次に、今登録したレコードがidが2で登録されています。
次に、order_productsを確認してみます。
console

mysql> select * from order_products;
+----+----------+--------------+------+-------+
| id | order_id | product_name | num  | price |
+----+----------+--------------+------+-------+
|  1 |        2 | バナナ       |    1 |   500 |
+----+----------+--------------+------+-------+
1 row in set (0.00 sec)

order_idが2で、カートに入れた商品が正しく入っていればOKです。

sessionをクリアする

購入が完了したら、カートの中身は空っぽにします。
foreachが終わったらsessionのproductsをunsetします。
pay_end.php

    //order_productsテーブル
    foreach($products as $key => $product){
        $stmt2 = $dbh->prepare("INSERT INTO order_products(order_id,product_name,num,price) VALUES(:order_id,:product_name,:num,:price)");
        $stmt2->bindParam(':order_id',$order_id);
        $stmt2->bindParam(':product_name',$key);
        $stmt2->bindParam(':num',$product['count']);
        $stmt2->bindParam(':price',$product['price']);
        $stmt2->execute();
    }

+   unset($_SESSION['products']);

?>

再度購入を実行してみて、DBに貯めるところまでできたらcart.phpにアクセスしてカートの中身を確認してみます。
カートの中身が空っぽであればOKです。

完了画面を作成する

DBにデータを貯められたので、HTMLで購入完了ページを作成します。
pay_end.phpのPHPの下にhtmlを記述します。
pay_conf.phpのhtml部分だけコピーして、titleやmainの中身を購入完了用に変更すればいいです。
pay_end.php

?>
<!DOCTYPE html>
<html>

<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-13xxxxxxxxx"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag() { dataLayer.push(arguments); }
        gtag('js', new Date());

        gtag('config', 'UA-13xxxxxxxxx');
    </script>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>購入完了|SQUARE, inc.</title>

    <link rel="icon" href="favicon.ico">

    <!-- css -->
    <link rel="stylesheet" href="styles.css">
    <link rel="stylesheet" href="responsive.css">

    <!-- icon -->
    <link href="https://use.fontawesome.com/releases/v5.0.6/css/all.css" rel="stylesheet">
</head>

<body>
    <header>
        <div class="container">
            <div class="header-logo">
                <h1><a href="index.php"><img src="img/square_logo.png" id="logo"></a></h1>
            </div>

            <!-- ハンバーガーメニューボタン -->
            <div class="toggle">
                <div>
                    <span></span>
                    <span></span>
                    <span></span>
                </div>
            </div>
            <div class="cart">
                <a href="cart.php"><i class="fas fa-shopping-cart"></i></a>
            </div>

            <nav class="sp-menu menu">
                <ul>
                    <li><a href="index.php#service">サービス</a></li>
                    <li><a href="shop.php">商品一覧</a></li>
                    <li><a href="index.php#news">お知らせ</a></li>
                    <li><a href="index.php#about">会社概要</a></li>
                    <li><a href="ブログのURL">ブログ</a></li>
                    <li><a href="register.html">会員登録</a></li>
                </ul>
            </nav>

            <nav class="pc-menu menu-left menu">
                <ul>
                    <li><a href="index.php#service">サービス</a></li>
                    <li><a href="shop.php">商品一覧</a></li>
                    <li><a href="index.php#news">お知らせ</a></li>
                    <li><a href="index.php#about">会社概要</a></li>
                    <li><a href="ブログのURL">ブログ</a></li>
                </ul>
            </nav>
            <nav class="pc-menu menu-right menu">
                <ul>
                    <li><a href="cart.php"><i class="fas fa-shopping-cart"></i></a></li>
                    <li><a href="register.html">会員登録</a></li>
                </ul>
            </nav>
        </div>
    </header>
    <main>
        <div class="breadcrumbs">
            <div class="container">
                <ul>
                    <li><a href="index.php">TOP</a></li>
                    <li><a href="cart.php">カート</a></li>
                    <li>購入完了</li>
                </ul>
            </div>
        </div>
        <div class="wrapper last-wrapper">
            <div class="container">
                <div class="wrapper-title">
                    <h3>購入完了</h3>
                </div>
                <div class="wrapper-body">
                    <div class="thanks">
                        <h4>ご購入ありがとうございました。</h4>
                    </div>
                    <button type="button" class="btn btn-gray" onclick="location.href='index.php'">トップページに戻る</button>
                </div>
            </div>
        </div>
    </main>
    <footer>
        <div class="container">
            <p>Copyright @ 2018 SQUARE, inc.</p>
        </div>
    </footer>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script>
        $(function () {
            // ハンバーガーメニューの動作
            $('.toggle').click(function () {
                $("header").toggleClass('open');
                $(".sp-menu").slideToggle(500);
            });
        });
    </script>
</body>

</html>

ここまでできたら、再度、商品一覧から購入を進めてみます。
最後に購入完了の画面が表示されればOKです。

これで注文する(注文を受け付ける)の実装が完了しました。

コード

https://github.com/bluecode-io/web-basic/tree/basic7-3