fix(message_bizppurio): SMS 대체발송 결과가 미요청 건에도 잘못 표시되던 문제 수정
비즈뿌리오가 대체발송 미요청 건에도 webhook TELRES/KAORES 값을 채워 보내 빈 문자열 검사만으로는 요청 여부를 구분할 수 없었다. 발송 시점에 우리가 실제로 대체발송을 요청했는지(request_payload의 resend 키 존재)를 판단 기준으로 바꿔, 미요청 건은 webhook 값과 무관하게 결과를 표시하지 않는다.
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
- 비회원 주문 알림도 주문 시 입력한 연락처로 문자를 받을 수 있습니다.
|
||||
- 카카오 알림톡 채널을 알림 설정에 추가했습니다. (알림톡 실제 발송·템플릿 연동은 후속 단계에서 제공됩니다.)
|
||||
- 비즈뿌리오가 보내는 발송 결과 통보를 받아 각 발송의 실제 도달 여부(성공/실패)를 기록합니다. 실패한 경우 사유를 함께 확인할 수 있습니다(예: 음영 지역, 잘못된 번호, 스팸 차단, 지갑 잔액 부족 등).
|
||||
- 관리자 "알림 발송 이력" 화면에서 문자·알림톡의 발송 결과(성공/실패·사유·지갑 잔액 부족·SMS 대체발송 여부)를 각 발송 행에서 바로 확인할 수 있습니다. 문자·알림톡 탭에서만 결과가 표시되며, 행을 펼치면 상세 결과를 볼 수 있습니다.
|
||||
- 관리자 "알림 발송 이력" 화면에서 문자·알림톡의 발송 결과(성공/실패·사유·지갑 잔액 부족·SMS 대체발송 여부)를 각 발송 행에서 바로 확인할 수 있습니다. 문자·알림톡 탭에서만 결과가 표시되며, 행을 펼치면 상세 결과를 볼 수 있습니다. 대체발송을 실제로 요청한 건에서만 결과가 표시되어, 사용하지 않은 건에 잘못 표시되지 않습니다. 알림톡은 카카오 승인 템플릿의 실제 발송 내용을 별도로 함께 보여줘, 위에 표시되는 알림 기본 본문과 헷갈리지 않도록 합니다.
|
||||
- 지갑 잔액 부족으로 발송이 실패하면 관리자에게 알립니다. 대량 실패 시 알림이 반복 발송되지 않도록 일정 시간 간격으로 한 번만 안내합니다.
|
||||
- 설정 페이지의 알림톡 템플릿 탭에서 카카오 알림톡 템플릿의 목록·상태·내용을 확인할 수 있습니다. 템플릿 등록·수정·검수·상태변경은 비즈뿌리오 콘솔에서 하며, 이 화면은 목록·상태·내용 조회와 알림 연결을 담당합니다. 콘솔에서 등록한 템플릿도 이 목록에 그대로 표시되고 알림에 연결할 수 있습니다.
|
||||
- 템플릿 목록을 상태(발송가능·검수중·반려·미검수·중지 등) 배지와 함께 보여주고, 상태·이름으로 필터·검색할 수 있습니다. 발송가능(승인) 상태의 템플릿만 알림에 연결해 발송할 수 있습니다.
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Services\PluginSettingsService;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Concerns\PreventsReplayWebhook;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Enums\DispatchStatus;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Enums\ResultCategory;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Models\BizppurioDispatch;
|
||||
use Plugins\Sirsoft\MessageBizppurio\Repositories\Contracts\BizppurioDispatchRepositoryInterface;
|
||||
|
||||
/**
|
||||
@@ -79,7 +80,7 @@ class WebhookReportService
|
||||
'media' => $report['MEDIA'] ?? null,
|
||||
'result_code' => $resultCode,
|
||||
'result_message' => $this->resolver->reason($resultCode),
|
||||
'fallback_status' => $this->resolveFallbackStatus($report),
|
||||
'fallback_status' => $this->resolveFallbackStatus($report, $dispatch),
|
||||
'reported_at' => now(),
|
||||
'raw_payload' => $report['raw'] ?? $report,
|
||||
]);
|
||||
@@ -131,11 +132,22 @@ class WebhookReportService
|
||||
/**
|
||||
* 대체발송 결과를 해석합니다 (webhook TELRES/KAORES).
|
||||
*
|
||||
* 비즈뿌리오는 대체발송 미요청 건에도 TELRES/KAORES 에 "0" 같은 값을 채워 보내므로
|
||||
* (실측), webhook 응답값만으로는 대체발송 발생 여부를 판단할 수 없다. 우리가 발송
|
||||
* 시점에 실제로 대체발송을 요청했는지(`request_payload` 의 resend/recontent 존재)를
|
||||
* 먼저 확인해, 요청하지 않았으면 webhook 값과 무관하게 null 을 반환한다.
|
||||
*
|
||||
* @param array<string, mixed> $report 리포트
|
||||
* @param BizppurioDispatch $dispatch webhook 매칭된 발송 이력 (발송 시점 요청 payload 보유)
|
||||
* @return string|null 대체발송 결과 코드 또는 null
|
||||
*/
|
||||
private function resolveFallbackStatus(array $report): ?string
|
||||
private function resolveFallbackStatus(array $report, BizppurioDispatch $dispatch): ?string
|
||||
{
|
||||
$requestPayload = $dispatch->request_payload ?? [];
|
||||
if (! isset($requestPayload['resend'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$telres = $report['TELRES'] ?? null;
|
||||
$kaores = $report['KAORES'] ?? null;
|
||||
|
||||
|
||||
+71
-1
@@ -40,9 +40,10 @@ class BizppurioWebhookTest extends PluginTestCase
|
||||
*
|
||||
* @param string $refkey 우리 부여 키
|
||||
* @param string $channel 채널
|
||||
* @param array<string, mixed>|null $requestPayload 발송 시점 요청 payload (resend 포함 여부 검증용)
|
||||
* @return BizppurioDispatch
|
||||
*/
|
||||
private function seedDispatch(string $refkey, string $channel = 'sms'): BizppurioDispatch
|
||||
private function seedDispatch(string $refkey, string $channel = 'sms', ?array $requestPayload = null): BizppurioDispatch
|
||||
{
|
||||
return BizppurioDispatch::create([
|
||||
'refkey' => $refkey,
|
||||
@@ -52,6 +53,7 @@ class BizppurioWebhookTest extends PluginTestCase
|
||||
'notification_type' => 'welcome',
|
||||
'status' => DispatchStatus::Sent->value,
|
||||
'source' => 'auto',
|
||||
'request_payload' => $requestPayload,
|
||||
'sent_at' => now(),
|
||||
]);
|
||||
}
|
||||
@@ -246,4 +248,72 @@ class BizppurioWebhookTest extends PluginTestCase
|
||||
->postJson(self::ENDPOINT, ['REFKEY' => 'ref_noauth', 'RESULT' => '4100'])
|
||||
->assertStatus(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 대체발송(SMS 대체발송)을 요청하지 않은 발송건(request_payload 에 resend 없음)은,
|
||||
* webhook 응답에 TELRES/KAORES 값이 채워져 와도 fallback_status 가 null 로 남아야 한다.
|
||||
*
|
||||
* 비즈뿌리오는 대체발송 미요청 건에도 TELRES="0" 같은 값을 채워 보내므로(실서버 관측:
|
||||
* alimtalk 대체발송 OFF 인데 결과 화면에 SMS 대체발송 뱃지가 표시된 회귀), webhook 값
|
||||
* 존재 여부만으로 판단하면 안 되고 우리가 실제로 요청했는지(request_payload)를 봐야 한다.
|
||||
*
|
||||
* @scenario source=webhook,ip=allowed,result=success,fallback_requested=false
|
||||
*
|
||||
* @effects fallback_status_remains_null_when_not_requested
|
||||
*/
|
||||
public function test_fallback_status_stays_null_when_resend_was_not_requested(): void
|
||||
{
|
||||
$dispatch = $this->seedDispatch(
|
||||
'ref_no_resend_requested',
|
||||
DispatchChannel::Alimtalk->value,
|
||||
['account' => 'sirsoft', 'content' => ['at' => ['templatecode' => 'tpl_1']]],
|
||||
);
|
||||
|
||||
$this->withServerVariables(['REMOTE_ADDR' => self::ALLOWED_IP])
|
||||
->postJson(self::ENDPOINT, [
|
||||
'REFKEY' => 'ref_no_resend_requested',
|
||||
'RESULT' => '7000',
|
||||
'MEDIA' => 'KAT',
|
||||
'TELRES' => '0',
|
||||
'KAORES' => '0',
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$dispatch->refresh();
|
||||
$this->assertSame(DispatchStatus::Success, $dispatch->status);
|
||||
$this->assertNull($dispatch->fallback_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 대체발송을 실제로 요청한 발송건(request_payload 에 resend 있음)은 webhook TELRES 값을
|
||||
* fallback_status 에 정상 반영한다.
|
||||
*
|
||||
* @scenario source=webhook,ip=allowed,result=balance_low,fallback_requested=true
|
||||
*
|
||||
* @effects fallback_status_set_when_resend_was_requested
|
||||
*/
|
||||
public function test_fallback_status_set_when_resend_was_requested(): void
|
||||
{
|
||||
$dispatch = $this->seedDispatch(
|
||||
'ref_resend_requested',
|
||||
DispatchChannel::Alimtalk->value,
|
||||
[
|
||||
'account' => 'sirsoft',
|
||||
'content' => ['at' => ['templatecode' => 'tpl_1']],
|
||||
'resend' => ['first' => 'sms'],
|
||||
],
|
||||
);
|
||||
|
||||
$this->withServerVariables(['REMOTE_ADDR' => self::ALLOWED_IP])
|
||||
->postJson(self::ENDPOINT, [
|
||||
'REFKEY' => 'ref_resend_requested',
|
||||
'RESULT' => '7436',
|
||||
'MEDIA' => 'KAT',
|
||||
'TELRES' => '4100',
|
||||
])
|
||||
->assertStatus(200);
|
||||
|
||||
$dispatch->refresh();
|
||||
$this->assertSame('4100', $dispatch->fallback_status);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user