サブスク管理アプリ SubsCut ~サブスカット~
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 | プロフィール画像の保存・削除 |
画面遷移図
モックアップ
ER図
インフラ構成図
使用イメージ
サブスクの登録
サブスクの解約
実際のサブスクを解約したら、Subscutアプリでも「解約ボタン」を押すと、ステータスが「解約済」に変わります。解約日の翌日以降は課金されていないという仕様で、棒グラフが表示されます。
グラフ表示
メールで課金日をお知らせ
毎日0:00にバッチでサブスクをチェック&更新。課金日の1週間前から16:00にメールでお知らせ。
プロフィール画像の保存・削除
テスト
手動テストを中心で、一部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は似ていると感じました。
最後まで読んでいただき、ありがとうございました!