diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/CHANGELOG.md b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/CHANGELOG.md index d4ec2274..75a430e1 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/CHANGELOG.md +++ b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/CHANGELOG.md @@ -10,3 +10,5 @@ - 비즈뿌리오 메시징 플러그인(sirsoft-message_bizppurio)의 일본어 번들 언어팩 초기 제공 - 환경설정·알림톡 템플릿 관리·발송 이력 등 관리자 화면 텍스트와 발송 결과 코드 안내의 일본어 번역 추가 — 관련 화면이 일본어 로케일에서 자연스럽게 표시됩니다. +- 후불 한도 초과(9071) 결과 코드 사유의 일본어 번역 추가. +- 잔액부족 알림의 채널군 라벨(문자/알림톡) 일본어 번역 추가. diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/messages.php b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/messages.php index b021ed3c..ca0770ef 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/messages.php +++ b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/messages.php @@ -72,4 +72,8 @@ return [ 'cache' => [ 'cleared' => 'アラートトークテンプレート内容キャッシュを初期化しました。次回送信から最新内容が反映されます。', ], + 'channel_group' => [ + 'text' => '文字', + 'alimtalk' => '通知トーク', + ], ]; diff --git a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/result_codes.php b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/result_codes.php index b7682ea9..13b492a0 100644 --- a/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/result_codes.php +++ b/lang-packs/_bundled/g7-plugin-sirsoft-message_bizppurio-ja/backend/ja/result_codes.php @@ -46,4 +46,5 @@ return [ '7523' => 'その他のエラー', '9000' => '一時的なシステムエラー', '9070' => '残高不足(メール)', + '9071' => '後払い限度額超過', ]; diff --git a/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md b/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md index c3927f55..63f03b92 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md +++ b/plugins/_bundled/sirsoft-message_bizppurio/CHANGELOG.md @@ -36,6 +36,7 @@ - 문자·알림톡 발송 시 비즈뿌리오에 실제로 전송한 요청 내용을 이력에 함께 저장합니다. 발송이 실패했을 때 어떤 내용이 전송됐는지 사후에 확인할 수 있습니다. - 알림톡 템플릿이 연결되지 않았거나 문자 템플릿이 없는 등 발송 자체를 시도하지 못한 경우, 관리자 알림 발송 이력 화면에 "성공"이 아닌 "실패"로 정확히 표시됩니다. - 플러그인을 삭제하면 이 플러그인이 알림 설정에 추가했던 문자·알림톡 채널이 함께 정리됩니다. 메일·사이트 내 알림 등 다른 채널과 알림 자체는 그대로 유지되며, 플러그인을 다시 설치하면 문자·알림톡 채널이 자동으로 복원됩니다. +- 잔액 부족으로 발송이 실패해 관리자에게 보내는 알림에서, 실패한 채널(문자/알림톡)과 결과 코드가 안내 문구에 실제 값으로 표시됩니다. 후불 결제 한도를 초과한 경우도 잔액 부족과 동일하게 관리자에게 알립니다. ### Changed diff --git a/plugins/_bundled/sirsoft-message_bizppurio/lang/en/messages.php b/plugins/_bundled/sirsoft-message_bizppurio/lang/en/messages.php index f901b492..d6281577 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/lang/en/messages.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/lang/en/messages.php @@ -8,6 +8,12 @@ return [ 'alimtalk' => 'Alimtalk', ], + // 채널 그룹 라벨 (SMS/LMS 를 "문자" 로 묶음 — 잔액부족 알림 등 채널군 표기용) + 'channel_group' => [ + 'text' => 'SMS', + 'alimtalk' => 'Alimtalk', + ], + // 발송 상태 (DispatchStatus enum) 'status' => [ 'pending' => 'Pending', diff --git a/plugins/_bundled/sirsoft-message_bizppurio/lang/en/result_codes.php b/plugins/_bundled/sirsoft-message_bizppurio/lang/en/result_codes.php index 7d83d64f..42c6b7b1 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/lang/en/result_codes.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/lang/en/result_codes.php @@ -9,7 +9,7 @@ declare(strict_types=1); | | Source: Bizppurio API manual "9. Status_Code" table | As of: 2026-07-13 (plan appendix C-3, phase 1 scope) -| Scope: common(2000~9070) + SMS(4100~4443) + LMS(6600~6641) + Alimtalk(7000~7523). +| Scope: common(2000~9071) + SMS(4100~4443) + LMS(6600~6641) + Alimtalk(7000~7523). | RCS(8000s)/NaverTalkTalk(part of 5000s) are out of scope (follow-up D15). | */ @@ -32,6 +32,7 @@ return [ '5005' => 'Temporary delivery error', '9000' => 'Temporary system error', '9070' => 'Insufficient balance (SMS)', + '9071' => 'Postpaid limit exceeded', // ── SMS report ─────────────────────────────────────── '4100' => 'Success', diff --git a/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/messages.php b/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/messages.php index a1fc0b42..df9b94eb 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/messages.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/messages.php @@ -8,6 +8,12 @@ return [ 'alimtalk' => '알림톡', ], + // 채널 그룹 라벨 (SMS/LMS 를 "문자" 로 묶음 — 잔액부족 알림 등 채널군 표기용) + 'channel_group' => [ + 'text' => '문자', + 'alimtalk' => '알림톡', + ], + // 발송 상태 (DispatchStatus enum) 'status' => [ 'pending' => '대기', diff --git a/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/result_codes.php b/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/result_codes.php index 89ec128c..d6387fc5 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/result_codes.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/lang/ko/result_codes.php @@ -9,7 +9,7 @@ declare(strict_types=1); | | 출처: 비즈뿌리오 API 매뉴얼 "9. Status_Code" 표 | 기준일: 2026-07-13 (계획서 부록 C-3 1차 범위) -| 범위: 공통(2000~9070) + SMS(4100~4443) + LMS(6600~6641) + 알림톡(7000~7523) +| 범위: 공통(2000~9071) + SMS(4100~4443) + LMS(6600~6641) + 알림톡(7000~7523) | RCS(8000대)·네이버톡톡(5000대 일부)은 범위 밖(후속 D15). | | 매뉴얼이 삭제되어도 이 파일이 원본 스펙을 보존한다. 새 코드 추가 시 분류는 @@ -35,6 +35,7 @@ return [ '5005' => '일시적 전송 오류', '9000' => '일시적 시스템 오류', '9070' => '잔액 부족(문자)', + '9071' => '후불 한도 초과', // ── SMS 리포트 ──────────────────────────────────────── '4100' => '성공', diff --git a/plugins/_bundled/sirsoft-message_bizppurio/plugin.php b/plugins/_bundled/sirsoft-message_bizppurio/plugin.php index 4e4923f2..4450cd31 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/plugin.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/plugin.php @@ -13,6 +13,7 @@ use Database\Seeders\NotificationDefinitionSeeder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Plugins\Sirsoft\MessageBizppurio\Http\Middleware\BizppurioWebhookIpWhitelist; +use Plugins\Sirsoft\MessageBizppurio\Listeners\BalanceLowNotificationDataListener; use Plugins\Sirsoft\MessageBizppurio\Listeners\GuestPhoneExtractListener; use Plugins\Sirsoft\MessageBizppurio\Listeners\LinkNotificationLogListener; use Plugins\Sirsoft\MessageBizppurio\Listeners\RegisterNotificationChannelsListener; @@ -447,6 +448,7 @@ class Plugin extends AbstractPlugin RegisterNotificationChannelsListener::class, SeedChannelTemplatesListener::class, GuestPhoneExtractListener::class, + BalanceLowNotificationDataListener::class, LinkNotificationLogListener::class, ValidateBizppurioSettingsListener::class, ]; diff --git a/plugins/_bundled/sirsoft-message_bizppurio/src/Listeners/BalanceLowNotificationDataListener.php b/plugins/_bundled/sirsoft-message_bizppurio/src/Listeners/BalanceLowNotificationDataListener.php new file mode 100644 index 00000000..996492f6 --- /dev/null +++ b/plugins/_bundled/sirsoft-message_bizppurio/src/Listeners/BalanceLowNotificationDataListener.php @@ -0,0 +1,110 @@ +> + */ + public static function getSubscribedHooks(): array + { + return [ + 'sirsoft-message_bizppurio.notification.extract_data' => [ + 'method' => 'injectBalanceLowData', + 'priority' => 10, + 'type' => 'filter', + ], + ]; + } + + /** + * 기본 핸들러 (미사용 — 필터 메서드로 처리). + * + * @param mixed ...$args + */ + public function handle(...$args): void {} + + /** + * 잔액부족 알림의 data 에 본문 치환 변수를 채웁니다. + * + * extract_data 결과(`{notifiable, notifiables, data, context}`)를 받아, 대상이 + * 잔액부족 알림이면 훅 인자(결과코드·채널)와 사이트 메타를 data 에 덧붙여 반환한다. + * 그 외 유형은 원본을 그대로 통과시킨다. `name` 은 코어가 수신자별로 치환하도록 + * `{recipient_name}` placeholder 를 넣는다(NotificationHookListener 폴백 규약). + * + * @param array $result extract_data 결과 + * @param string $type 알림 정의 유형 + * @param array $args 훅 원본 인수 ([$resultCode, $channel]) + * @return array 변수가 채워진(또는 원본) 결과 + */ + public function injectBalanceLowData(array $result, string $type, array $args): array + { + if ($type !== self::NOTIFICATION_TYPE) { + return $result; + } + + $resultCode = (string) ($args[0] ?? ''); + $channel = (string) ($args[1] ?? ''); + + $result['data'] = array_merge( + $result['data'] ?? [], + [ + 'name' => '{recipient_name}', + 'app_name' => (string) config('app.name', ''), + 'result_code' => $resultCode, + 'channel_label' => $this->channelLabel($channel), + 'settings_url' => rtrim((string) config('app.url', ''), '/').self::SETTINGS_PATH, + 'site_url' => (string) config('app.url', ''), + ], + ); + + return $result; + } + + /** + * 채널 문자열을 잔액부족 알림용 채널군 라벨(문자/알림톡)로 변환합니다. + * + * SMS/LMS 는 "문자" 로 묶고 알림톡은 "알림톡" 으로 표기한다. 알 수 없는 채널은 + * 원본 문자열을 그대로 반환한다(치환 실패보다 원본 노출이 안전). + * + * @param string $channel 채널 문자열 (sms/lms/alimtalk) + * @return string 채널군 라벨 + */ + private function channelLabel(string $channel): string + { + $enum = DispatchChannel::tryFrom($channel); + + if ($enum === null) { + return $channel; + } + + return $enum->isText() + ? __('sirsoft-message_bizppurio::messages.channel_group.text') + : __('sirsoft-message_bizppurio::messages.channel_group.alimtalk'); + } +} \ No newline at end of file diff --git a/plugins/_bundled/sirsoft-message_bizppurio/src/Services/ResultCodeResolver.php b/plugins/_bundled/sirsoft-message_bizppurio/src/Services/ResultCodeResolver.php index ce003f89..190468bf 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/src/Services/ResultCodeResolver.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/src/Services/ResultCodeResolver.php @@ -34,9 +34,13 @@ class ResultCodeResolver private const RETRYABLE_CODES = ['5003', '5004', '5005', '9000', '3011', '3013', '7306', '7307', '7421', '7437']; /** - * 잔액 부족 코드 — 문자 9070 / 알림톡 7436 (D3 자체 알림 대상). + * 잔액 부족 코드 — 선불 잔액부족 9070 / 알림톡 지갑 잔액부족 7436 / + * 후불 한도초과 9071 (D3 자체 알림 대상). + * + * 셋 다 "잔액·한도 소진으로 발송 불가" 성격이라 동일하게 관리자 자체 알림을 발화한다. + * 표시 사유는 result_codes lang 으로 각각 구분된다(9071 = 후불 한도 초과). */ - private const BALANCE_LOW_CODES = ['9070', '7436']; + private const BALANCE_LOW_CODES = ['9070', '7436', '9071']; /** * 결과 코드를 카테고리로 분류합니다. diff --git a/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Listeners/BalanceLowNotificationDataListenerTest.php b/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Listeners/BalanceLowNotificationDataListenerTest.php new file mode 100644 index 00000000..eb099957 --- /dev/null +++ b/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Listeners/BalanceLowNotificationDataListenerTest.php @@ -0,0 +1,127 @@ +listener = new BalanceLowNotificationDataListener; + } + + private function emptyResult(): array + { + return ['notifiable' => null, 'notifiables' => null, 'data' => [], 'context' => []]; + } + + /** + * 문자(sms) 잔액부족: channel_label="문자", result_code 주입 + 사이트 메타 채움. + */ + public function test_injects_variables_for_sms_balance_low(): void + { + $out = $this->listener->injectBalanceLowData( + $this->emptyResult(), + 'bizppurio_balance_low', + ['9070', 'sms'], + ); + + $data = $out['data']; + $this->assertSame('9070', $data['result_code']); + $this->assertSame('문자', $data['channel_label']); + $this->assertSame('{recipient_name}', $data['name']); + $this->assertNotSame('', $data['app_name']); + $this->assertStringContainsString('/admin/plugins/sirsoft-message_bizppurio/settings', $data['settings_url']); + $this->assertArrayHasKey('site_url', $data); + } + + /** + * 알림톡(alimtalk) 잔액부족: channel_label="알림톡", result_code=7436. + */ + public function test_injects_variables_for_alimtalk_balance_low(): void + { + $out = $this->listener->injectBalanceLowData( + $this->emptyResult(), + 'bizppurio_balance_low', + ['7436', 'alimtalk'], + ); + + $this->assertSame('7436', $out['data']['result_code']); + $this->assertSame('알림톡', $out['data']['channel_label']); + } + + /** + * lms 도 문자군으로 "문자" 라벨. + */ + public function test_lms_maps_to_text_label(): void + { + $out = $this->listener->injectBalanceLowData( + $this->emptyResult(), + 'bizppurio_balance_low', + ['9071', 'lms'], + ); + + $this->assertSame('문자', $out['data']['channel_label']); + $this->assertSame('9071', $out['data']['result_code']); + } + + /** + * 다른 알림 유형은 원본을 그대로 통과(변수 미주입). + */ + public function test_ignores_other_notification_types(): void + { + $result = $this->emptyResult(); + $out = $this->listener->injectBalanceLowData($result, 'order_confirmed', ['9070', 'sms']); + + $this->assertSame($result, $out); + $this->assertArrayNotHasKey('result_code', $out['data']); + } + + /** + * 알 수 없는 채널은 원본 문자열을 라벨로 유지(치환 실패보다 원본 노출). + */ + public function test_unknown_channel_keeps_raw(): void + { + $out = $this->listener->injectBalanceLowData( + $this->emptyResult(), + 'bizppurio_balance_low', + ['9999', 'unknown'], + ); + + $this->assertSame('unknown', $out['data']['channel_label']); + } + + /** + * 주입된 data 로 알림 본문 템플릿의 변수가 실제 값으로 치환된다(엔드투엔드). + */ + public function test_injected_data_resolves_template_body(): void + { + $out = $this->listener->injectBalanceLowData( + $this->emptyResult(), + 'bizppurio_balance_low', + ['9071', 'alimtalk'], + ); + + $body = '비즈뿌리오 잔액 부족으로 {channel_label} 발송이 실패했습니다 (코드: {result_code}).'; + $rendered = strtr($body, [ + '{channel_label}' => $out['data']['channel_label'], + '{result_code}' => $out['data']['result_code'], + ]); + + $this->assertSame('비즈뿌리오 잔액 부족으로 알림톡 발송이 실패했습니다 (코드: 9071).', $rendered); + $this->assertStringNotContainsString('{', $rendered); + } +} \ No newline at end of file diff --git a/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Services/ResultCodeResolverTest.php b/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Services/ResultCodeResolverTest.php index 24a5e517..9cdcaa7b 100644 --- a/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Services/ResultCodeResolverTest.php +++ b/plugins/_bundled/sirsoft-message_bizppurio/tests/Unit/Services/ResultCodeResolverTest.php @@ -33,17 +33,26 @@ class ResultCodeResolverTest extends PluginTestCase } /** - * 잔액부족(9070 문자 / 7436 알림톡)은 BalanceLow + isBalanceLow true. + * 잔액부족(9070 문자 / 7436 알림톡 / 9071 후불 한도초과)은 BalanceLow + isBalanceLow true. */ public function test_balance_low_codes(): void { - foreach (['9070', '7436'] as $code) { + foreach (['9070', '7436', '9071'] as $code) { $this->assertSame(ResultCategory::BalanceLow, $this->resolver->categorize($code), "코드 {$code}"); $this->assertTrue($this->resolver->isBalanceLow($code), "코드 {$code}"); $this->assertFalse($this->resolver->isSuccess($code), "코드 {$code}"); } } + /** + * 후불 한도초과(9071) 는 사유 문구가 lang 에 정의되어 코드만 노출되지 않는다. + */ + public function test_postpaid_limit_code_has_reason(): void + { + $this->assertNotNull($this->resolver->reason('9071')); + $this->assertStringContainsString('9071', $this->resolver->label('9071')); + } + /** * 일시오류 코드는 Retry. *