Files
Gnuboard7/plugins/_bundled/sirsoft-message_bizppurio/src/Services/BizppurioTokenService.php
T
HeuJung 0bccd867a7 chore(release): 7.0.9 출시 준비 — 출시일자 정렬·공개 배포물 정합 결함 정리
7.0.9 릴리즈 사전 점검에서 공개 배포물에 실릴 결함을 전수 정리한다.

출시일자: 이번 사이클에 버전이 오르거나 새로 추가된 대상 10건의 CHANGELOG
출시일을 실제 공개일로 정렬. 결제 3종은 직전 릴리즈 페이로드로 이미 나갔으므로
제외했다.

내부 역할 호칭: 공개 배포물 26파일 49곳을 중립 표현으로 치환. 이미 공개된
페이로드에는 실질 0건이라 이번 릴리즈가 첫 유입이었다.

최초 출시 규격: 신규 플러그인과 그 언어팩의 CHANGELOG 를 선례 규격으로
재구성. 작성 중 확인되지 않은 서술 2건은 코드 실측으로 교정했다.

번들 ja 언어팩: 번역 값에 남은 한글 40건 정정. 같은 용어가 세 갈래로 갈려
있던 표기도 함께 통일했다. 정정값의 근거는 저장소가 이미 갖고 있었다 —
테스트가 정답으로 단언하는 값과 배포물이 어긋난 상태였다.

재발 방지: 원인은 번역 생성기의 용어집 미등록이므로 출력만 고치면 다음
빌드에서 되살아난다. 용어집에 8건을 등록하고, 규정 3건과 잔존 검출 룰을
신설했다. 룰은 수정 전 배포물로 red 가 되는지를 픽스처로 고정해 판정기가
모집단에 닿는지를 증명한다.
2026-08-24 16:18:52 +09:00

210 lines
7.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Plugins\Sirsoft\MessageBizppurio\Services;
use App\Contracts\Extension\CacheInterface;
use App\Services\PluginSettingsService;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
use Plugins\Sirsoft\MessageBizppurio\Exceptions\BizppurioApiException;
/**
* 비즈뿌리오 발송 시스템(api.bizppurio.com) 인증 토큰 서비스.
*
* `/v1/token` 을 Basic 인증(계정:암호 Base64)으로 호출해 Bearer 토큰을 발급받고,
* 확장 도메인 캐시(CacheInterface)에 저장한다. 토큰 유효 시간은 24시간이나, 만료
* 경계 재발급 리스크를 피하기 위해 23시간 TTL 로 캐시한다(제품 결정, 계획서 §5).
*
* 발송 응답이 3002(토큰무효)/3005(인증정보무효)를 반환하면 캐시를 forget 하고
* 1회 재발급한다(BizppurioApiClient 가 재시도 트리거). CacheInterface 는
* MessageBizppurioServiceProvider 의 $cacheServices contextual binding 으로 주입된다.
*/
class BizppurioTokenService
{
/** 플러그인 식별자 (manifest 와 일치) */
private const PLUGIN_IDENTIFIER = 'sirsoft-message_bizppurio';
/** 토큰 캐시 키 (확장 도메인 캐시 접두사가 자동 적용됨) */
private const CACHE_KEY = 'bizppurio:token';
/** 토큰 캐시 TTL (초) — 23시간. 유효 24h 대비 만료 경계 회피 */
private const CACHE_TTL_SECONDS = 23 * 3600;
/** 운영 발송 도메인 */
private const HOST_LIVE = 'https://api.bizppurio.com';
/** 검수 발송 도메인 */
private const HOST_DEV = 'https://dev-api.bizppurio.com';
private const CONNECT_TIMEOUT_SECONDS = 5;
private const REQUEST_TIMEOUT_SECONDS = 15;
/**
* @param CacheInterface $cache 확장 도메인 캐시 드라이버(contextual binding 주입)
* @param PluginSettingsService $pluginSettings 플러그인 환경설정 조회
*/
public function __construct(
private readonly CacheInterface $cache,
private readonly PluginSettingsService $pluginSettings,
) {}
/**
* 캐시된 토큰을 반환하거나, 없으면 새로 발급받아 캐시합니다.
*
* @return string Bearer 액세스 토큰
*
* @throws BizppurioApiException 토큰 발급 실패 시
*/
public function getToken(): string
{
return $this->cache->remember(
self::CACHE_KEY,
fn (): string => $this->issueToken(),
self::CACHE_TTL_SECONDS,
);
}
/**
* 캐시된 토큰을 무효화하고 새 토큰을 발급받아 반환합니다.
*
* 발송 응답이 3002/3005(토큰·인증 무효)일 때 호출한다.
*
* @return string 새로 발급받은 Bearer 액세스 토큰
*
* @throws BizppurioApiException 토큰 재발급 실패 시
*/
public function refreshToken(): string
{
$this->cache->forget(self::CACHE_KEY);
return $this->getToken();
}
/**
* 캐시된 토큰을 제거합니다.
*/
public function forget(): void
{
$this->cache->forget(self::CACHE_KEY);
}
/**
* 현재 저장된 자격증명으로 토큰 발급을 즉시 재검증합니다.
*
* 캐시를 거치지 않고 매번 `/v1/token` 을 새로 호출한다(관리자가 계정/비밀번호가
* 유효한지 그 자리에서 확인하려는 목적 — 설정 화면 "연결 확인" 버튼이 소비).
* 검증에 성공하면 새로 발급된 토큰으로 캐시를 갱신해, 확인 직후의 발송이 이
* 토큰을 그대로 재사용할 수 있게 한다(불필요한 재발급 방지).
*
* @return string 새로 발급받은 Bearer 액세스 토큰
*
* @throws BizppurioApiException 자격증명 미설정·HTTP 실패·응답 파싱 실패 시
*/
public function verifyCredentials(): string
{
$token = $this->issueToken();
$this->cache->put(self::CACHE_KEY, $token, self::CACHE_TTL_SECONDS);
return $token;
}
/**
* `/v1/token` 을 호출해 새 토큰을 발급받습니다.
*
* @return string Bearer 액세스 토큰
*
* @throws BizppurioApiException 자격증명 미설정·HTTP 실패·응답 파싱 실패 시
*/
private function issueToken(): string
{
$settings = $this->pluginSettings->get(self::PLUGIN_IDENTIFIER) ?? [];
$account = (string) ($settings['bizppurio_id'] ?? '');
$password = (string) ($settings['password'] ?? '');
if ($account === '' || $password === '') {
throw new BizppurioApiException(
__('sirsoft-message_bizppurio::messages.error.credentials_missing'),
);
}
$response = $this->http()
->withHeaders([
'Authorization' => 'Basic '.base64_encode($account.':'.$password),
'Content-Type' => 'application/json; charset=utf-8',
])
->post($this->baseUrl($settings).'/v1/token');
if ($response->failed()) {
throw new BizppurioApiException(
$this->describeFailure($response),
resultCode: (string) ($response->json('code') ?? '') ?: null,
httpStatus: $response->status(),
);
}
$token = (string) ($response->json('accesstoken') ?? '');
if ($token === '') {
throw new BizppurioApiException(
$this->describeFailure($response),
resultCode: (string) ($response->json('code') ?? '') ?: null,
httpStatus: $response->status(),
);
}
return $token;
}
/**
* 토큰 발급 실패 응답에서 비즈뿌리오 원문 사유를 담은 메시지를 만듭니다.
*
* 비즈뿌리오는 실패 시 `{"code": "3007", "description": "invalid password in
* bizppurio"}` 형태로 원인을 내려준다. 원문이 있으면 함께 노출해 운영자가 계정/
* 비밀번호 오류인지 서버 오류인지 즉시 구분할 수 있게 한다(고정 문구만으로는
* 원인 추적이 불가능했던 문제 대응).
*
* @param \Illuminate\Http\Client\Response $response 실패한 HTTP 응답
* @return string 사용자에게 보여줄 실패 메시지
*/
private function describeFailure(\Illuminate\Http\Client\Response $response): string
{
$description = (string) ($response->json('description') ?? '');
if ($description === '') {
return __('sirsoft-message_bizppurio::messages.error.token_issue_failed');
}
return __('sirsoft-message_bizppurio::messages.error.token_issue_failed_with_reason', [
'reason' => $description,
]);
}
/**
* 환경(운영/검수)에 맞는 발송 도메인 베이스 URL 을 반환합니다.
*
* @param array<string, mixed> $settings 플러그인 환경설정
* @return string 베이스 URL (스킴 포함)
*/
private function baseUrl(array $settings): string
{
// 검수 모드(is_test_mode)가 꺼져 있으면 운영 도메인으로 발송한다. 기본값(미설정)은
// 안전하게 검수(true)로 간주해 운영 발송이 우발적으로 일어나지 않도록 한다.
$isTestMode = (bool) ($settings['is_test_mode'] ?? true);
return $isTestMode ? self::HOST_DEV : self::HOST_LIVE;
}
/**
* 공통 타임아웃이 적용된 HTTP 클라이언트를 반환합니다.
*/
private function http(): PendingRequest
{
return Http::connectTimeout(self::CONNECT_TIMEOUT_SECONDS)
->timeout(self::REQUEST_TIMEOUT_SECONDS);
}
}