From 83ddfabd7cf5bdf2df1ef972c24dc2139ce99698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 21 Feb 2026 00:11:03 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[card]=20=EC=B9=B4=EB=93=9C=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=88=98=EC=A0=95/=EC=A0=80=EC=9E=A5=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=20=ED=95=84=EB=93=9C=209=EA=B0=9C=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 마이그레이션: card_type, alias, cvc_encrypted, payment_day, total_limit, used_amount, remaining_limit, is_manual, memo 컬럼 추가 - Card 모델: $fillable, $casts, $hidden 확장 + CVC 암호화/복호화 메서드 추가 - CardService: store(), update() 메서드에 9개 필드 처리 로직 추가 - StoreCardRequest, UpdateCardRequest: 9개 필드 검증 규칙 추가 --- .../Requests/V1/Card/StoreCardRequest.php | 9 ++++ .../Requests/V1/Card/UpdateCardRequest.php | 9 ++++ app/Models/Tenants/Card.php | 51 +++++++++++++++++++ app/Services/CardService.php | 39 ++++++++++++++ ...00000_add_detail_fields_to_cards_table.php | 46 +++++++++++++++++ 5 files changed, 154 insertions(+) create mode 100644 database/migrations/2026_02_21_100000_add_detail_fields_to_cards_table.php diff --git a/app/Http/Requests/V1/Card/StoreCardRequest.php b/app/Http/Requests/V1/Card/StoreCardRequest.php index 20123e9..72051ab 100644 --- a/app/Http/Requests/V1/Card/StoreCardRequest.php +++ b/app/Http/Requests/V1/Card/StoreCardRequest.php @@ -15,12 +15,21 @@ public function rules(): array { return [ 'card_company' => ['required', 'string', 'max:50'], + 'card_type' => ['nullable', 'string', 'max:50'], 'card_number' => ['required', 'string', 'regex:/^\d{13,19}$/'], 'expiry_date' => ['required', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{2}$/'], 'card_password' => ['nullable', 'string', 'size:2', 'regex:/^\d{2}$/'], 'card_name' => ['required', 'string', 'max:100'], + 'alias' => ['nullable', 'string', 'max:100'], + 'csv' => ['nullable', 'string', 'max:4'], + 'payment_day' => ['nullable', 'integer', 'min:1', 'max:31'], + 'total_limit' => ['nullable', 'numeric', 'min:0'], + 'used_amount' => ['nullable', 'numeric', 'min:0'], + 'remaining_limit' => ['nullable', 'numeric', 'min:0'], 'status' => ['nullable', 'string', 'in:active,inactive'], + 'is_manual' => ['nullable', 'boolean'], 'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'], + 'memo' => ['nullable', 'string', 'max:500'], ]; } diff --git a/app/Http/Requests/V1/Card/UpdateCardRequest.php b/app/Http/Requests/V1/Card/UpdateCardRequest.php index 2506ab6..d9cb68d 100644 --- a/app/Http/Requests/V1/Card/UpdateCardRequest.php +++ b/app/Http/Requests/V1/Card/UpdateCardRequest.php @@ -15,12 +15,21 @@ public function rules(): array { return [ 'card_company' => ['sometimes', 'string', 'max:50'], + 'card_type' => ['nullable', 'string', 'max:50'], 'card_number' => ['sometimes', 'string', 'regex:/^\d{13,19}$/'], 'expiry_date' => ['sometimes', 'string', 'regex:/^(0[1-9]|1[0-2])\/\d{2}$/'], 'card_password' => ['nullable', 'string', 'size:2', 'regex:/^\d{2}$/'], 'card_name' => ['sometimes', 'string', 'max:100'], + 'alias' => ['nullable', 'string', 'max:100'], + 'csv' => ['nullable', 'string', 'max:4'], + 'payment_day' => ['nullable', 'integer', 'min:1', 'max:31'], + 'total_limit' => ['nullable', 'numeric', 'min:0'], + 'used_amount' => ['nullable', 'numeric', 'min:0'], + 'remaining_limit' => ['nullable', 'numeric', 'min:0'], 'status' => ['sometimes', 'string', 'in:active,inactive'], + 'is_manual' => ['nullable', 'boolean'], 'assigned_user_id' => ['nullable', 'integer', 'exists:users,id'], + 'memo' => ['nullable', 'string', 'max:500'], ]; } diff --git a/app/Models/Tenants/Card.php b/app/Models/Tenants/Card.php index eb1eaf7..757f04c 100644 --- a/app/Models/Tenants/Card.php +++ b/app/Models/Tenants/Card.php @@ -22,8 +22,17 @@ * @property string $expiry_date * @property string|null $card_password_encrypted * @property string $card_name + * @property string|null $card_type + * @property string|null $alias + * @property string|null $cvc_encrypted + * @property int|null $payment_day + * @property float|null $total_limit + * @property float|null $used_amount + * @property float|null $remaining_limit * @property string $status + * @property bool $is_manual * @property int|null $assigned_user_id + * @property string|null $memo * @property int|null $created_by * @property int|null $updated_by * @property int|null $deleted_by @@ -37,13 +46,22 @@ class Card extends Model protected $fillable = [ 'tenant_id', 'card_company', + 'card_type', 'card_number_encrypted', 'card_number_last4', 'expiry_date', 'card_password_encrypted', 'card_name', + 'alias', + 'cvc_encrypted', + 'payment_day', + 'total_limit', + 'used_amount', + 'remaining_limit', 'status', + 'is_manual', 'assigned_user_id', + 'memo', 'created_by', 'updated_by', 'deleted_by', @@ -52,12 +70,25 @@ class Card extends Model protected $hidden = [ 'card_number_encrypted', 'card_password_encrypted', + 'cvc_encrypted', ]; protected $attributes = [ 'status' => 'active', + 'is_manual' => false, ]; + protected function casts(): array + { + return [ + 'payment_day' => 'integer', + 'total_limit' => 'decimal:2', + 'used_amount' => 'decimal:2', + 'remaining_limit' => 'decimal:2', + 'is_manual' => 'boolean', + ]; + } + // ========================================================================= // 관계 정의 // ========================================================================= @@ -127,6 +158,26 @@ public function getDecryptedCardPassword(): ?string : null; } + /** + * CVC/CVV 암호화 설정 + */ + public function setCvc(?string $cvc): void + { + $this->cvc_encrypted = $cvc + ? Crypt::encryptString($cvc) + : null; + } + + /** + * CVC/CVV 복호화 조회 + */ + public function getDecryptedCvc(): ?string + { + return $this->cvc_encrypted + ? Crypt::decryptString($this->cvc_encrypted) + : null; + } + // ========================================================================= // 헬퍼 메서드 // ========================================================================= diff --git a/app/Services/CardService.php b/app/Services/CardService.php index 4558c8f..9eb1fd8 100644 --- a/app/Services/CardService.php +++ b/app/Services/CardService.php @@ -87,11 +87,19 @@ public function store(array $data): Card $card = new Card; $card->tenant_id = $tenantId; $card->card_company = $data['card_company']; + $card->card_type = $data['card_type'] ?? null; $card->setCardNumber($data['card_number']); $card->expiry_date = $data['expiry_date']; $card->card_name = $data['card_name']; + $card->alias = $data['alias'] ?? null; + $card->payment_day = $data['payment_day'] ?? null; + $card->total_limit = $data['total_limit'] ?? null; + $card->used_amount = $data['used_amount'] ?? null; + $card->remaining_limit = $data['remaining_limit'] ?? null; $card->status = $data['status'] ?? 'active'; + $card->is_manual = $data['is_manual'] ?? false; $card->assigned_user_id = $data['assigned_user_id'] ?? null; + $card->memo = $data['memo'] ?? null; $card->created_by = $userId; $card->updated_by = $userId; @@ -99,6 +107,10 @@ public function store(array $data): Card $card->setCardPassword($data['card_password']); } + if (array_key_exists('csv', $data)) { + $card->setCvc($data['csv']); + } + $card->save(); return $card; @@ -121,6 +133,9 @@ public function update(int $id, array $data): Card if (isset($data['card_company'])) { $card->card_company = $data['card_company']; } + if (array_key_exists('card_type', $data)) { + $card->card_type = $data['card_type']; + } if (isset($data['card_number'])) { $card->setCardNumber($data['card_number']); } @@ -130,12 +145,36 @@ public function update(int $id, array $data): Card if (isset($data['card_name'])) { $card->card_name = $data['card_name']; } + if (array_key_exists('alias', $data)) { + $card->alias = $data['alias']; + } + if (array_key_exists('csv', $data)) { + $card->setCvc($data['csv']); + } + if (array_key_exists('payment_day', $data)) { + $card->payment_day = $data['payment_day']; + } + if (array_key_exists('total_limit', $data)) { + $card->total_limit = $data['total_limit']; + } + if (array_key_exists('used_amount', $data)) { + $card->used_amount = $data['used_amount']; + } + if (array_key_exists('remaining_limit', $data)) { + $card->remaining_limit = $data['remaining_limit']; + } if (isset($data['status'])) { $card->status = $data['status']; } + if (array_key_exists('is_manual', $data)) { + $card->is_manual = $data['is_manual']; + } if (array_key_exists('assigned_user_id', $data)) { $card->assigned_user_id = $data['assigned_user_id']; } + if (array_key_exists('memo', $data)) { + $card->memo = $data['memo']; + } if (isset($data['card_password'])) { $card->setCardPassword($data['card_password']); } diff --git a/database/migrations/2026_02_21_100000_add_detail_fields_to_cards_table.php b/database/migrations/2026_02_21_100000_add_detail_fields_to_cards_table.php new file mode 100644 index 0000000..5902baa --- /dev/null +++ b/database/migrations/2026_02_21_100000_add_detail_fields_to_cards_table.php @@ -0,0 +1,46 @@ +string('card_type', 50)->nullable()->after('card_company')->comment('카드 종류 (corporate_1, personal 등)'); + $table->string('alias', 100)->nullable()->after('card_name')->comment('카드 별칭'); + $table->text('cvc_encrypted')->nullable()->after('card_password_encrypted')->comment('암호화된 CVC/CVV'); + $table->unsignedTinyInteger('payment_day')->nullable()->after('cvc_encrypted')->comment('결제일 (1-31)'); + $table->decimal('total_limit', 15, 2)->nullable()->after('payment_day')->comment('총 한도'); + $table->decimal('used_amount', 15, 2)->nullable()->after('total_limit')->comment('사용 금액'); + $table->decimal('remaining_limit', 15, 2)->nullable()->after('used_amount')->comment('잔여 한도'); + $table->boolean('is_manual')->default(false)->after('status')->comment('수기 등록 여부'); + $table->text('memo')->nullable()->after('assigned_user_id')->comment('메모'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('cards', function (Blueprint $table) { + $table->dropColumn([ + 'card_type', + 'alias', + 'cvc_encrypted', + 'payment_day', + 'total_limit', + 'used_amount', + 'remaining_limit', + 'is_manual', + 'memo', + ]); + }); + } +};