feat: complete Spring Boot 카드 매입·정산 system (acquire-core migration target, mvn verify green, 24 tests)
This commit is contained in:
commit
0f6e3acfe6
92 changed files with 4320 additions and 0 deletions
35
modules/payment/pom.xml
Normal file
35
modules/payment/pom.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>card-acquiring-boot</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>payment</artifactId>
|
||||
<name>payment</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>persistence</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.klaro.acquiring.payment;
|
||||
|
||||
import com.klaro.acquiring.common.codec.FieldSpec;
|
||||
import com.klaro.acquiring.common.codec.MessageCodec;
|
||||
import com.klaro.acquiring.common.codec.MessageSpec;
|
||||
import com.klaro.acquiring.common.error.NotFoundException;
|
||||
import com.klaro.acquiring.common.util.DateUtil;
|
||||
import com.klaro.acquiring.domain.entity.Merchant;
|
||||
import com.klaro.acquiring.domain.entity.Payment;
|
||||
import com.klaro.acquiring.domain.entity.Settlement;
|
||||
import com.klaro.acquiring.domain.enums.PaymentStatus;
|
||||
import com.klaro.acquiring.persistence.repository.MerchantRepository;
|
||||
import com.klaro.acquiring.persistence.repository.PaymentRepository;
|
||||
import com.klaro.acquiring.persistence.repository.SettlementRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 지급(py) 서비스. 정산 결과로 지급 파일(고정길이 전문)을 생성하고 지급결과를 반영한다.
|
||||
*/
|
||||
@Service
|
||||
public class PaymentService {
|
||||
|
||||
/** 지급 레코드 전문 레이아웃 (은행 송금 원장 파일 1행). */
|
||||
public static final MessageSpec PAYMENT_SPEC = MessageSpec.of("PAY_REC",
|
||||
FieldSpec.an("BANKCODE", 3),
|
||||
FieldSpec.an("ACCOUNTNO", 20),
|
||||
FieldSpec.num("AMOUNT", 15),
|
||||
FieldSpec.an("MERCHID", 15),
|
||||
FieldSpec.an("PAYDATE", 8));
|
||||
|
||||
private static final MessageCodec CODEC = new MessageCodec(PAYMENT_SPEC);
|
||||
|
||||
private final SettlementRepository settlementRepository;
|
||||
private final MerchantRepository merchantRepository;
|
||||
private final PaymentRepository paymentRepository;
|
||||
|
||||
public PaymentService(SettlementRepository settlementRepository,
|
||||
MerchantRepository merchantRepository,
|
||||
PaymentRepository paymentRepository) {
|
||||
this.settlementRepository = settlementRepository;
|
||||
this.merchantRepository = merchantRepository;
|
||||
this.paymentRepository = paymentRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* 해당 지급일의 정산건에 대해 지급 레코드를 생성하고, 고정길이 지급파일 문자열을 반환.
|
||||
*/
|
||||
@Transactional
|
||||
public PaymentFile generateFile(LocalDate payDate) {
|
||||
List<Settlement> settlements = settlementRepository.findAll().stream()
|
||||
.filter(s -> s.getPayDate().equals(payDate))
|
||||
.toList();
|
||||
|
||||
StringBuilder file = new StringBuilder();
|
||||
int count = 0;
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (Settlement s : settlements) {
|
||||
if (s.getNetAmount().signum() <= 0) {
|
||||
continue;
|
||||
}
|
||||
Merchant m = merchantRepository.findById(s.getMerchId())
|
||||
.orElseThrow(() -> new NotFoundException("가맹점 없음: " + s.getMerchId()));
|
||||
|
||||
Payment payment = new Payment(s.getSettlementId(), m.getMerchId(), payDate,
|
||||
s.getNetAmount(), m.getBankCode(), m.getAccountNo());
|
||||
payment.setStatus(PaymentStatus.FILE_CREATED);
|
||||
paymentRepository.save(payment);
|
||||
|
||||
Map<String, String> rec = new LinkedHashMap<>();
|
||||
rec.put("BANKCODE", m.getBankCode() == null ? "" : m.getBankCode());
|
||||
rec.put("ACCOUNTNO", m.getAccountNo() == null ? "" : m.getAccountNo());
|
||||
rec.put("AMOUNT", s.getNetAmount().toBigInteger().toString());
|
||||
rec.put("MERCHID", m.getMerchId());
|
||||
rec.put("PAYDATE", DateUtil.format(payDate));
|
||||
file.append(CODEC.encode(rec)).append('\n');
|
||||
count++;
|
||||
total = total.add(s.getNetAmount());
|
||||
}
|
||||
return new PaymentFile(payDate, count, total, file.toString());
|
||||
}
|
||||
|
||||
/** 지급 결과 반영(성공/실패). */
|
||||
@Transactional
|
||||
public Payment applyResult(Long paymentId, boolean success) {
|
||||
Payment p = paymentRepository.findById(paymentId)
|
||||
.orElseThrow(() -> new NotFoundException("지급 없음: " + paymentId));
|
||||
p.setStatus(success ? PaymentStatus.PAID : PaymentStatus.FAILED);
|
||||
return paymentRepository.save(p);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Payment> listByPayDate(LocalDate payDate) {
|
||||
return paymentRepository.findByPayDate(payDate);
|
||||
}
|
||||
|
||||
/** 지급 파일 생성 결과. */
|
||||
public record PaymentFile(LocalDate payDate, int count, BigDecimal totalAmount, String content) {
|
||||
}
|
||||
}
|
||||
Reference in a new issue