0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ポートフォリオ】Laravel + AWS + jQueryで構築したサブスク管理アプリ「SubsCut」の解説

Last updated at Posted at 2025-05-17

サブスク管理アプリ  SubsCut ~サブスカット~

portfolio-movie-ezgif.com-video-to-gif-converter.gif
URL https://subscut.site
GitHub https://github.com/cafelatte00/subscut_aws
自作したアプリの設計〜実装までの流れをご紹介いたします!

使用技術

項目 内容
バックエンド PHP 8 / Laravel 9
フロントエンド JavaScript / jQuery / Chart.js
メールサーバー Gmail SMTP
インフラ AWS(EC2 / RDS[MySQL] / S3 / Route 53 / ACM / IAM)

作成背景

お試し感覚で入ったサブスクの課金日が過ぎてしまい、もったいないと思ったのが、作成のきっかけです。

アプリ概要

  • サブスクを登録し、いつでも一覧から次回課金日・料金等を確認できる
  • 課金日の1週間前からメールで通知
  • 過去2年分の課金額をグラフで確認できる

機能一覧

機能
1 ログイン機能
2 サブスクの登録(CRUD) 登録は非同期通信
3 サブスクの解約
4 グラフ表示機能 Chart.js
5 ページネーション機能
6 ゲストユーザーログイン機能
7 バッチでサブスク更新・メールで課金日通知
8 プロフィール画像の保存・削除

画面遷移図

※企画段階と実装段階で、変更した箇所があります。
スクリーンショット 2025-05-18 20.16.29.png

モックアップ

※企画段階と実装段階で、デザイン変更しています。
スクリーンショット 2025-05-18 20.35.04.png

ER図

スクリーンショット 2025-05-18 21.18.44.png
ツール:https://dbdiagram.io

インフラ構成図

AWS.drawio.png

使用イメージ

サブスクの登録

サブスク登録は非同期通信を使用。
2025-05-1712.52.28-ezgif.com-speed.gif

サブスクの一覧表示・詳細・編集・削除は同期通信。
2025-05-1716.35.47-ezgif.com-speed.gif

サブスクの解約

実際のサブスクを解約したら、Subscutアプリでも「解約ボタン」を押すと、ステータスが「解約済」に変わります。解約日の翌日以降は課金されていないという仕様で、棒グラフが表示されます。
2025-05-1714.04.15-ezgif.com-speed.gif

グラフ表示

過去2年間の課金額が月別で表示されます。
2025-05-1713.30.24-ezgif.com-speed.gif

メールで課金日をお知らせ

毎日0:00にバッチでサブスクをチェック&更新。課金日の1週間前から16:00にメールでお知らせ。
スクリーンショット 2025-05-18 16.22.38.png

プロフィール画像の保存・削除

画面収録_2025_05_17_16_57_17_V1.gif

テスト

手動テストを中心で、一部PHPUnitを取り入れました。

・手動テスト
本番環境での動作確認は、スプレッドシートで管理し検証しました。
https://docs.google.com/spreadsheets/d/1faCWt7w9OxA7q8PvEJ3xfrJYhpJlIxT1TvgiqOAYA1k/edit?usp=sharing

・PHPUnit
Featureテスト サブスクリプション登録機能テスト

/**
 * 概要: addSubscriptionメソッドが新しいサブスクを作成し、JSONレスポンスを返すことを確認する
 * 条件: 有効なサブスクデータをPOSTリクエストとして送信する
 * 結果: ステータスコード200が返され、データベースに新しいサブスクが存在する
 */
public function test_addSubscriptionメソッドが新しいサブスクを新規作成し、JSONレスポンスを返すことを確認する()
{
    $data = [
        'user_id' => $this->user->id,
        'title' => 'Test Subscription',
        'price' => 1,
        'frequency' => 1,
        'first_payment_day' => '2025-03-01',
        'url' => 'http://test.com',
        'memo' => 'Test memo',
    ];

    $response = $this->postJson(route('subscriptions.add.subscription'), $data);

    $response->assertStatus(200);
    $this->assertDatabaseHas('subscriptions', ['title' => 'Test Subscription']);
}

 
Unitテスト 支払い関連のサービス(次回支払日と支払い回数の計算)のテスト

    /**
     * 概要: CheckSubscriptionService::calculatePaymentDetails() メソッドが、正しい次回支払日と支払い回数の値を返すことを確認す
     * 条件: 初回支払日が過去、支払い頻度が 1(1ヶ月) の引数を用意し、calculatePaymentDetails() メソッドを実行する。
     * 結果: メソッドの戻り値が 次回支払日は2025-04-01と同じ、支払い回数は3であること。
     * 注意:次回支払日の日付はテストするに日によって書き換えること。
     */
    public function test_初回支払日が過去の場合、正しい次回支払日と支払い回数を返す()
    {
        $firstPaymentDay = Carbon::parse('2025-01-01');
        $frequency = 1;

        $paymentDetails = CheckSubscriptionService::calculatePaymentDetails($firstPaymentDay, $frequency);

        $this->assertEquals(Carbon::parse('2025-04-01'), $paymentDetails['nextPaymentDay']);

        $this->assertEquals(3, $paymentDetails['numberOfPayments']);
    }

気をつけた点

月によって月末日が違うため、次回支払日がずれないようテストを何回も行いながら気をつけて実装しました。

・実装の一部を掲載します
app/Services/CheckSubscriptionService.php

    /**
     * 初回支払日と頻度から次回支払日と支払い回数を計算します。
     *
     * @param Carbon $firstPaymentDay 初回支払日
     * @param int $frequency 支払い頻度(月単位)
     * @return array 次回支払日と支払い回数を含む連想配列
     */
    public static function calculatePaymentDetails(Carbon $firstPaymentDay, int $frequency): array
{
    $today = Carbon::today();
    $nextPaymentDay = null;
    $numberOfPayments = 0;

    if ($today->eq($firstPaymentDay)) {
        // 本日が初回支払日の場合
        $nextPaymentDay = $firstPaymentDay->copy()->addMonthNoOverflow($frequency);
        $numberOfPayments = 1;
    } elseif ($today->lt($firstPaymentDay)) {
        // 初回支払日が未来の場合
        $nextPaymentDay = $firstPaymentDay;
    } else {
        // 初回支払日が過去の場合
        $numberOfPayments = 1;
        $calcPaymentDay = $firstPaymentDay->copy()->addMonthNoOverflow($frequency);
        $nextPaymentDay = $calcPaymentDay;

        // 計算用の支払日より今日が大きい間、繰り返す
        while ($today->gte($calcPaymentDay)) {
            $numberOfPayments++;
            $calcPaymentDay = $calcPaymentDay->copy()->addMonthNoOverflow($frequency);
            $nextPaymentDay = $calcPaymentDay;
        }
    }

    return [
        'nextPaymentDay' => $nextPaymentDay,
        'numberOfPayments' => $numberOfPayments,
    ];
}

感想

ポートフォリを作ってみて、企画したことが形になっていくのが楽しかったです!またMVCモデルなのでRuby on Rails とLaravelは似ていると感じました。
最後まで読んでいただき、ありがとうございました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?