feat: P6 외부연동 어댑터 모듈 PoC (펌뱅킹/SMS·카카오)
신규 모듈 ga-external-adapter — 외부 SDK 의존성을 격리. 실제 SDK 통합 시 이 모듈의 구현체만 교체하면 ga-api/ga-common 무변경. - ga-common.adapter.bank: BankTransferAdapter 인터페이스 + Request/Response/Status - ga-common.adapter.message: MessageAdapter 인터페이스 + Channel(SMS/KAKAO)/Request/Response - ga-external-adapter: MockBankTransferAdapter / MockMessageAdapter (로그 + 마스킹) - V82: withdraw_request.transfer_tx_id/transfer_status/transfer_at + 부분 인덱스 - ApprovalService.handleApprovalCallback(WITHDRAW) — 최종 승인 시 펌뱅킹 어댑터 호출 - 성공: status APPROVED → SENT 전이, transfer_tx_id 영속화 - 실패: APPROVED 유지 + fail_reason 기록 (재시도 배치는 추후) - ApprovalService.sendStepNotification — DB 알림 외 SMS 어댑터 호출 (non-critical) - WithdrawRequestMapper.updateTransferResult 추가
This commit is contained in:
@@ -10,5 +10,6 @@ jar.enabled = false
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':ga-core')
|
implementation project(':ga-core')
|
||||||
|
implementation project(':ga-external-adapter')
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package com.ga.api.service.approval;
|
package com.ga.api.service.approval;
|
||||||
|
|
||||||
|
import com.ga.common.adapter.bank.BankTransferAdapter;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferRequest;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferResponse;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferStatus;
|
||||||
|
import com.ga.common.adapter.message.MessageAdapter;
|
||||||
|
import com.ga.common.adapter.message.MessageChannel;
|
||||||
|
import com.ga.common.adapter.message.MessageRequest;
|
||||||
import com.ga.common.exception.BizException;
|
import com.ga.common.exception.BizException;
|
||||||
import com.ga.common.exception.ErrorCode;
|
import com.ga.common.exception.ErrorCode;
|
||||||
import com.ga.common.model.PageResponse;
|
import com.ga.common.model.PageResponse;
|
||||||
@@ -12,6 +19,7 @@ import com.ga.core.mapper.settle.SettleMasterMapper;
|
|||||||
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
|
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
|
||||||
import com.ga.core.vo.approval.*;
|
import com.ga.core.vo.approval.*;
|
||||||
import com.ga.core.vo.notice.NotificationRefType;
|
import com.ga.core.vo.notice.NotificationRefType;
|
||||||
|
import com.ga.core.vo.withdraw.WithdrawRequestVO;
|
||||||
// Note: ApprovalLineVO kept for createLine/updateLine methods
|
// Note: ApprovalLineVO kept for createLine/updateLine methods
|
||||||
import com.ga.core.vo.notice.UserNotificationVO;
|
import com.ga.core.vo.notice.UserNotificationVO;
|
||||||
import com.ga.core.vo.settle.SettleStatus;
|
import com.ga.core.vo.settle.SettleStatus;
|
||||||
@@ -41,6 +49,10 @@ public class ApprovalService {
|
|||||||
private final CommissionDisputeMapper disputeMapper;
|
private final CommissionDisputeMapper disputeMapper;
|
||||||
private final IncentivePaymentMapper incentivePaymentMapper;
|
private final IncentivePaymentMapper incentivePaymentMapper;
|
||||||
|
|
||||||
|
// P6 외부연동 어댑터 — 실제 구현은 ga-external-adapter 모듈 (현재 Mock)
|
||||||
|
private final BankTransferAdapter bankTransferAdapter;
|
||||||
|
private final MessageAdapter messageAdapter;
|
||||||
|
|
||||||
// ── Line ──────────────────────────────────────────────────────────────────
|
// ── Line ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public PageResponse<ApprovalLineResp> listLines(ApprovalLineSearchParam param) {
|
public PageResponse<ApprovalLineResp> listLines(ApprovalLineSearchParam param) {
|
||||||
@@ -228,6 +240,7 @@ public class ApprovalService {
|
|||||||
case "WITHDRAW" -> {
|
case "WITHDRAW" -> {
|
||||||
withdrawMapper.updateStatus(targetId, "APPROVED", null);
|
withdrawMapper.updateStatus(targetId, "APPROVED", null);
|
||||||
log.info("출금신청 승인 완료: requestId={}", targetId);
|
log.info("출금신청 승인 완료: requestId={}", targetId);
|
||||||
|
requestBankTransfer(targetId);
|
||||||
}
|
}
|
||||||
case "SETTLE" -> {
|
case "SETTLE" -> {
|
||||||
settleMasterMapper.updateStatus(targetId, SettleStatus.CONFIRMED.name(),
|
settleMasterMapper.updateStatus(targetId, SettleStatus.CONFIRMED.name(),
|
||||||
@@ -252,7 +265,7 @@ public class ApprovalService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 다음 결재 단계 담당자에게 알림 발송 */
|
/** 다음 결재 단계 담당자에게 알림 발송 (DB + 외부채널) */
|
||||||
private void sendStepNotification(Long requestId, Long lineId, int stepNo, String targetType) {
|
private void sendStepNotification(Long requestId, Long lineId, int stepNo, String targetType) {
|
||||||
try {
|
try {
|
||||||
ApprovalLineStepVO step = stepMapper.selectByLineAndStep(lineId, stepNo);
|
ApprovalLineStepVO step = stepMapper.selectByLineAndStep(lineId, stepNo);
|
||||||
@@ -260,14 +273,79 @@ public class ApprovalService {
|
|||||||
// roleCode 기반 userId 매핑은 P5에서 고도화 — 현재는 메타만 저장
|
// roleCode 기반 userId 매핑은 P5에서 고도화 — 현재는 메타만 저장
|
||||||
UserNotificationVO noti = new UserNotificationVO();
|
UserNotificationVO noti = new UserNotificationVO();
|
||||||
noti.setUserId(0L); // 실제 담당자 ID 조회 로직은 P5
|
noti.setUserId(0L); // 실제 담당자 ID 조회 로직은 P5
|
||||||
noti.setTitle("[결재요청] " + (targetType != null ? targetType : "") + " 결재 " + stepNo + "단계");
|
String title = "[결재요청] " + (targetType != null ? targetType : "") + " 결재 " + stepNo + "단계";
|
||||||
noti.setContent("결재 요청 ID " + requestId + " 에 대한 " + stepNo + "단계 결재가 필요합니다.");
|
String content = "결재 요청 ID " + requestId + " 에 대한 " + stepNo + "단계 결재가 필요합니다.";
|
||||||
|
noti.setTitle(title);
|
||||||
|
noti.setContent(content);
|
||||||
noti.setRefType(NotificationRefType.APPROVAL.name());
|
noti.setRefType(NotificationRefType.APPROVAL.name());
|
||||||
noti.setRefId(requestId);
|
noti.setRefId(requestId);
|
||||||
noti.setIsRead(false);
|
noti.setIsRead(false);
|
||||||
notificationMapper.insert(noti);
|
notificationMapper.insert(noti);
|
||||||
|
|
||||||
|
// SMS/카카오 외부채널 — Mock 단계라 항상 시도. 실패해도 DB 알림은 살아있다.
|
||||||
|
sendExternalMessage(MessageChannel.SMS, title, content, requestId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("결재 알림 발송 실패 (non-critical): requestId={}", requestId, e);
|
log.warn("결재 알림 발송 실패 (non-critical): requestId={}", requestId, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 출금 결재 최종 승인 → 펌뱅킹 어댑터 요청.
|
||||||
|
* <p>
|
||||||
|
* 어댑터가 실패 응답을 줘도 결재 자체는 이미 APPROVED 이므로 예외를 던지지 않는다.
|
||||||
|
* (재시도 배치는 transfer_status='FAILED' 필터로 별도 구현 예정.)
|
||||||
|
*/
|
||||||
|
private void requestBankTransfer(Long withdrawId) {
|
||||||
|
try {
|
||||||
|
WithdrawRequestVO vo = withdrawMapper.selectVOById(withdrawId);
|
||||||
|
if (vo == null) {
|
||||||
|
log.warn("[BANK] withdraw 조회 실패: requestId={}", withdrawId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BankTransferRequest req = BankTransferRequest.builder()
|
||||||
|
.withdrawRequestId(withdrawId)
|
||||||
|
.bankCode(vo.getBankCode())
|
||||||
|
.accountNo(vo.getAccountNo()) // 복호화 평문
|
||||||
|
.amount(vo.getAmount())
|
||||||
|
.memo("GA수수료 출금 #" + withdrawId)
|
||||||
|
.build();
|
||||||
|
BankTransferResponse resp = bankTransferAdapter.requestTransfer(req);
|
||||||
|
|
||||||
|
boolean ok = resp != null && resp.getStatus() != BankTransferStatus.FAILED;
|
||||||
|
String nextStatus = ok ? "SENT" : "APPROVED";
|
||||||
|
String failReason = ok ? null : (resp != null ? resp.getResultMessage() : "no response");
|
||||||
|
withdrawMapper.updateTransferResult(
|
||||||
|
withdrawId,
|
||||||
|
resp != null ? resp.getTxId() : null,
|
||||||
|
resp != null && resp.getStatus() != null ? resp.getStatus().name() : null,
|
||||||
|
nextStatus,
|
||||||
|
failReason
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 어댑터 자체가 예외를 던진 경우 — APPROVED 상태로 두고 fail_reason 만 갱신.
|
||||||
|
log.error("[BANK] 펌뱅킹 어댑터 호출 실패: requestId={}", withdrawId, e);
|
||||||
|
try {
|
||||||
|
withdrawMapper.updateTransferResult(
|
||||||
|
withdrawId, null, BankTransferStatus.FAILED.name(),
|
||||||
|
"APPROVED", e.getMessage());
|
||||||
|
} catch (Exception ignore) { /* 결재 흐름 깨지지 않게 swallow */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** SMS/카카오 어댑터로 메시지 전송 — Mock 단계라 PoC 용 phone "010-0000-0000" 고정. */
|
||||||
|
private void sendExternalMessage(MessageChannel channel, String title, String content, Long refId) {
|
||||||
|
try {
|
||||||
|
MessageRequest req = MessageRequest.builder()
|
||||||
|
.channel(channel)
|
||||||
|
.phoneNumber("010-0000-0000") // P6 보강: user.phone 조회로 교체
|
||||||
|
.title(title)
|
||||||
|
.content(content)
|
||||||
|
.refType(NotificationRefType.APPROVAL.name())
|
||||||
|
.refId(refId)
|
||||||
|
.build();
|
||||||
|
messageAdapter.send(req);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[MSG] {} 발송 실패 (non-critical): refId={}", channel, refId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ga.common.adapter.bank;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 펌뱅킹(이체) 외부연동 어댑터.
|
||||||
|
* <p>
|
||||||
|
* 구현체는 ga-external-adapter 모듈에 위치한다.
|
||||||
|
* 실제 KFTC/은행 SDK 연동 시 이 인터페이스만 새 구현으로 교체하면 된다.
|
||||||
|
*/
|
||||||
|
public interface BankTransferAdapter {
|
||||||
|
|
||||||
|
/** 단건 이체 요청. 외부 시스템 응답을 그대로 반환 (영속화는 호출자 책임). */
|
||||||
|
BankTransferResponse requestTransfer(BankTransferRequest request);
|
||||||
|
|
||||||
|
/** 이전 거래의 현재 상태 조회 (대사용). */
|
||||||
|
BankTransferResponse queryStatus(String txId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ga.common.adapter.bank;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class BankTransferRequest {
|
||||||
|
private Long withdrawRequestId; // 원천 출금 신청 id (멱등키)
|
||||||
|
private String bankCode;
|
||||||
|
private String accountNo; // 평문 (어댑터 내부에서 마스킹 로그)
|
||||||
|
private String accountHolderName;
|
||||||
|
private BigDecimal amount;
|
||||||
|
private String memo; // 통장 적요
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ga.common.adapter.bank;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class BankTransferResponse {
|
||||||
|
private String txId; // 외부 거래 id
|
||||||
|
private BankTransferStatus status; // ACCEPTED/SETTLED/FAILED 등
|
||||||
|
private String resultCode; // 외부 응답 코드
|
||||||
|
private String resultMessage; // 외부 응답 메시지
|
||||||
|
private LocalDateTime processedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.ga.common.adapter.bank;
|
||||||
|
|
||||||
|
public enum BankTransferStatus {
|
||||||
|
ACCEPTED, // 접수
|
||||||
|
SETTLED, // 입금 완료
|
||||||
|
FAILED // 실패
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.ga.common.adapter.message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS/카카오 알림톡 외부연동 어댑터.
|
||||||
|
* 구현체는 ga-external-adapter 모듈에 위치.
|
||||||
|
*/
|
||||||
|
public interface MessageAdapter {
|
||||||
|
|
||||||
|
/** 단건 메시지 전송. */
|
||||||
|
MessageResponse send(MessageRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.ga.common.adapter.message;
|
||||||
|
|
||||||
|
public enum MessageChannel {
|
||||||
|
SMS,
|
||||||
|
KAKAO
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ga.common.adapter.message;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class MessageRequest {
|
||||||
|
private MessageChannel channel;
|
||||||
|
private String phoneNumber; // 수신자 (E.164 또는 010-xxxx-xxxx)
|
||||||
|
private String title; // 카카오 알림톡 강조 헤더 용 (SMS는 무시 가능)
|
||||||
|
private String content;
|
||||||
|
private String templateCode; // 카카오 발신 템플릿 코드 (sms 미사용)
|
||||||
|
private Long refUserId; // 추적용 — user_notification.user_id
|
||||||
|
private String refType; // APPROVAL/SETTLE 등
|
||||||
|
private Long refId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ga.common.adapter.message;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class MessageResponse {
|
||||||
|
private String messageId; // 외부 시스템 발급 id
|
||||||
|
private boolean sent;
|
||||||
|
private String resultCode;
|
||||||
|
private String resultMessage;
|
||||||
|
private LocalDateTime sentAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- V82: withdraw_request 에 펌뱅킹 외부연동 결과 추적 컬럼 추가.
|
||||||
|
-- 어댑터(BankTransferAdapter) 호출 결과(거래ID/상태/처리시각)를 저장하기 위함.
|
||||||
|
|
||||||
|
ALTER TABLE withdraw_request
|
||||||
|
ADD COLUMN IF NOT EXISTS transfer_tx_id VARCHAR(64),
|
||||||
|
ADD COLUMN IF NOT EXISTS transfer_status VARCHAR(20),
|
||||||
|
ADD COLUMN IF NOT EXISTS transfer_at TIMESTAMP;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN withdraw_request.transfer_tx_id IS '펌뱅킹 외부 거래 ID (멱등키)';
|
||||||
|
COMMENT ON COLUMN withdraw_request.transfer_status IS 'ACCEPTED/SETTLED/FAILED — BankTransferStatus';
|
||||||
|
COMMENT ON COLUMN withdraw_request.transfer_at IS '펌뱅킹 호출/응답 시각';
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_wr_transfer_status
|
||||||
|
ON withdraw_request(transfer_status)
|
||||||
|
WHERE transfer_status IS NOT NULL;
|
||||||
@@ -20,4 +20,11 @@ public interface WithdrawRequestMapper {
|
|||||||
int markSent(@Param("requestId") Long requestId);
|
int markSent(@Param("requestId") Long requestId);
|
||||||
int markCompleted(@Param("requestId") Long requestId);
|
int markCompleted(@Param("requestId") Long requestId);
|
||||||
int cancel(@Param("requestId") Long requestId);
|
int cancel(@Param("requestId") Long requestId);
|
||||||
|
|
||||||
|
/** 펌뱅킹 어댑터 응답을 저장 (transfer_tx_id/status/at + 거래상태에 따른 status 전이). */
|
||||||
|
int updateTransferResult(@Param("requestId") Long requestId,
|
||||||
|
@Param("txId") String txId,
|
||||||
|
@Param("transferStatus") String transferStatus,
|
||||||
|
@Param("nextStatus") String nextStatus,
|
||||||
|
@Param("failReason") String failReason);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,4 +140,17 @@
|
|||||||
AND status IN ('REQUESTED', 'APPROVED')
|
AND status IN ('REQUESTED', 'APPROVED')
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
<!-- 펌뱅킹 어댑터 응답 저장 -->
|
||||||
|
<update id="updateTransferResult">
|
||||||
|
UPDATE withdraw_request
|
||||||
|
SET transfer_tx_id = #{txId,jdbcType=VARCHAR},
|
||||||
|
transfer_status = #{transferStatus,jdbcType=VARCHAR},
|
||||||
|
transfer_at = NOW(),
|
||||||
|
status = #{nextStatus},
|
||||||
|
fail_reason = #{failReason,jdbcType=VARCHAR},
|
||||||
|
<if test="nextStatus == 'SENT'">sent_at = NOW(),</if>
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE request_id = #{requestId}
|
||||||
|
</update>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// ga-external-adapter: 외부 시스템 연동 어댑터 구현체 모듈 (라이브러리)
|
||||||
|
// - 펌뱅킹/SMS/카카오 등 외부 SDK 의존성을 이 모듈에 격리한다.
|
||||||
|
// - PoC 단계: Mock 구현만 제공. 실제 SDK 통합 시 이 모듈에서만 의존성/구현 교체.
|
||||||
|
dependencies {
|
||||||
|
api project(':ga-common')
|
||||||
|
}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package com.ga.external.bank;
|
||||||
|
|
||||||
|
import com.ga.common.adapter.bank.BankTransferAdapter;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferRequest;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferResponse;
|
||||||
|
import com.ga.common.adapter.bank.BankTransferStatus;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 펌뱅킹 Mock 구현.
|
||||||
|
* <p>
|
||||||
|
* 실제 KFTC/은행 SDK 가 들어오기 전까지 사용. 결과는 항상 ACCEPTED 반환 + 로그.
|
||||||
|
* 계좌번호는 뒷 4자리 외 마스킹해 로그에 남긴다.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class MockBankTransferAdapter implements BankTransferAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BankTransferResponse requestTransfer(BankTransferRequest request) {
|
||||||
|
String masked = maskAccount(request.getAccountNo());
|
||||||
|
String txId = "MOCK-" + UUID.randomUUID();
|
||||||
|
log.info("[MOCK BANK] requestTransfer withdrawId={} bank={} acc={} amount={} txId={}",
|
||||||
|
request.getWithdrawRequestId(), request.getBankCode(), masked, request.getAmount(), txId);
|
||||||
|
|
||||||
|
return BankTransferResponse.builder()
|
||||||
|
.txId(txId)
|
||||||
|
.status(BankTransferStatus.ACCEPTED)
|
||||||
|
.resultCode("0000")
|
||||||
|
.resultMessage("MOCK accepted")
|
||||||
|
.processedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BankTransferResponse queryStatus(String txId) {
|
||||||
|
log.info("[MOCK BANK] queryStatus txId={}", txId);
|
||||||
|
return BankTransferResponse.builder()
|
||||||
|
.txId(txId)
|
||||||
|
.status(BankTransferStatus.SETTLED)
|
||||||
|
.resultCode("0000")
|
||||||
|
.resultMessage("MOCK settled")
|
||||||
|
.processedAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskAccount(String accountNo) {
|
||||||
|
if (accountNo == null) return null;
|
||||||
|
String digits = accountNo.replaceAll("[^0-9]", "");
|
||||||
|
if (digits.length() <= 4) return "****";
|
||||||
|
return "****" + digits.substring(digits.length() - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.ga.external.message;
|
||||||
|
|
||||||
|
import com.ga.common.adapter.message.MessageAdapter;
|
||||||
|
import com.ga.common.adapter.message.MessageRequest;
|
||||||
|
import com.ga.common.adapter.message.MessageResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMS / 카카오 알림톡 Mock 구현.
|
||||||
|
* <p>
|
||||||
|
* 실제 NHN Toast/알리고/카카오 비즈메시지 SDK 가 들어오기 전까지 사용. 항상 성공 응답.
|
||||||
|
* phoneNumber 는 뒷 4자리 외 마스킹.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class MockMessageAdapter implements MessageAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageResponse send(MessageRequest request) {
|
||||||
|
String masked = maskPhone(request.getPhoneNumber());
|
||||||
|
String msgId = "MOCK-MSG-" + UUID.randomUUID();
|
||||||
|
log.info("[MOCK MSG] send channel={} to={} title={} refType={} refId={} msgId={}",
|
||||||
|
request.getChannel(), masked, request.getTitle(),
|
||||||
|
request.getRefType(), request.getRefId(), msgId);
|
||||||
|
|
||||||
|
return MessageResponse.builder()
|
||||||
|
.messageId(msgId)
|
||||||
|
.sent(true)
|
||||||
|
.resultCode("0000")
|
||||||
|
.resultMessage("MOCK sent")
|
||||||
|
.sentAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String maskPhone(String phone) {
|
||||||
|
if (phone == null) return null;
|
||||||
|
String digits = phone.replaceAll("[^0-9]", "");
|
||||||
|
if (digits.length() <= 4) return "****";
|
||||||
|
return "***-****-" + digits.substring(digits.length() - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,3 +9,4 @@ include 'ga-core'
|
|||||||
include 'ga-api'
|
include 'ga-api'
|
||||||
include 'ga-batch'
|
include 'ga-batch'
|
||||||
include 'ga-admin'
|
include 'ga-admin'
|
||||||
|
include 'ga-external-adapter'
|
||||||
|
|||||||
Reference in New Issue
Block a user