Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class PlatformApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ public ResponseEntity<PayResult> createPayment(@Valid @RequestBody PaymentReques
null,
paymentRequest.installment(),
paymentRequest.payMethod(),
paymentRequest.payProvider()
paymentRequest.payProvider(),
null
);

paymentUseCase.doApproval(command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.commerce.platform.core.application.in.dto.PayCancelCommand;
import com.commerce.platform.core.application.in.dto.PayOrderCommand;
import com.commerce.platform.core.application.out.*;
import com.commerce.platform.core.application.out.dto.PgPayCancelResponse;
import com.commerce.platform.core.application.out.dto.PgPayResponse;
import com.commerce.platform.core.domain.aggreate.*;
import com.commerce.platform.core.domain.service.PaymentPgRouter;
Expand All @@ -15,7 +16,6 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;

import static com.commerce.platform.shared.exception.BusinessError.*;

Expand All @@ -39,7 +39,7 @@ public void doApproval(PayOrderCommand payOrdercommand) {
orderEntity.validForPay();

// pg사 라우팅
PgStrategy pgStrategy = pgRouter.routPg(payOrdercommand.getPayMethod());
PgStrategy pgStrategy = pgRouter.routePg(payOrdercommand.getPayMethod(), payOrdercommand.getPayProvider());

// 결재 entity 생성
payOrdercommand.setApprovedAmount(orderEntity.getResultAmt());
Expand All @@ -48,6 +48,8 @@ public void doApproval(PayOrderCommand payOrdercommand) {
// pg 결제 응답 수신
PgPayResponse pgResponse = pgStrategy.processApproval(payOrdercommand);

validRequestAmount(payOrdercommand.getApprovedAmount(), pgResponse.amount().value());

// 결제 결과에 따른 주문/결제 상태 변경
orderEntity.changeStatusAfterPay(pgResponse);
paymentEntity.approved(pgResponse);
Expand Down Expand Up @@ -85,14 +87,17 @@ public void doCancel(PayCancelCommand cancelCommand) {
cancelCommand.setPayMethod(paymentEntity.getPayMethod());
cancelCommand.setPgProvider(paymentEntity.getPgProvider());

PgStrategy pgStrategy = pgRouter.getPgStrategyByProvider(paymentEntity.getPgProvider());
PgPayResponse pgResponse = pgStrategy.processCancel(cancelCommand);
PgStrategy pgStrategy = pgRouter.getPgStrategyByProvider(paymentEntity.getPgProvider(), paymentEntity.getPayMethod());
PgPayCancelResponse pgResponse = pgStrategy.processCancel(cancelCommand);

// PG 응답 반영
if (!pgResponse.isSuccess()) {
throw new BusinessException(PG_RESPONSE_FAILED);
}

// Pg취소금액, 요청취소금액 검증
validRequestAmount(cancelCommand.getCanceledAmount(), pgResponse.cancelAmount());

orderEntity.refund();
paymentEntity.canceled(pgResponse);
paymentOutPort.savePayment(paymentEntity);
Expand Down Expand Up @@ -158,20 +163,22 @@ public Long doPartCancel(PayCancelCommand cancelCommand) {
cancelCommand.setPayMethod(paymentEntity.getPayMethod());
cancelCommand.setPgProvider(paymentEntity.getPgProvider());

PgStrategy pgStrategy = pgRouter.getPgStrategyByProvider(paymentEntity.getPgProvider());
PgPayResponse pgResponse = pgStrategy.processCancel(cancelCommand);
PgStrategy pgStrategy = pgRouter.getPgStrategyByProvider(paymentEntity.getPgProvider(), paymentEntity.getPayMethod());
PgPayCancelResponse pgResponse = pgStrategy.processCancel(cancelCommand);

if (!pgResponse.isSuccess()) {
throw new BusinessException(PG_RESPONSE_FAILED);
}

validRequestAmount(cancelCommand.getCanceledAmount(), pgResponse.cancelAmount());

// 부분취소 내역 저장
PaymentPartCancel partCancel = PaymentPartCancel.create(
paymentEntity.getPaymentId(),
canceledAmt,
refreshRemainAmt
);
partCancel.completed(pgResponse.pgTid());
partCancel.completed(pgResponse.pgCcTid());
paymentOutPort.savePartCancel(partCancel);

return partCancel.getId();
Expand All @@ -188,4 +195,14 @@ public void doApprovalWithCardId(Long cardId) {

}

/**
* pg 요청금액 , 고객 요청금액 동일 검증
* @param customerRequestAmt
* @param pgResponseAmt
*/
private void validRequestAmount(Money customerRequestAmt, Long pgResponseAmt) {
if(customerRequestAmt.value() != pgResponseAmt) {
throw new RuntimeException("요청금액 불일치");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,39 @@
import com.commerce.platform.core.domain.vo.Money;
import com.commerce.platform.core.domain.vo.OrderId;
import com.commerce.platform.core.domain.vo.Quantity;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.*;

/**
* 전체/부분 취소 처리 객체
*/
@Setter
@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class PayCancelCommand {
private OrderId orderId;
private Long orderItemId; // 취소할 orderItem
private Quantity canceledQuantity; // 해당 orderItem의 취소 개수
private PaymentStatus paymentStatus;
private String cancelReason;

// 이후 계산 및 db데이터 기반으로 세팅됨
// 이후 계산 및 db데이터 기반으로 세팅됨]
private String pgTid; // pg 승인Tid
private Money canceledAmount;
private PayMethod payMethod;
private PayProvider payProvider;
private PgProvider pgProvider;

private RefundReceiveAccount refundReceiveAccount;

/**
* 환불 계좌 정보 (가상계좌 전용)
*/
@Getter
@AllArgsConstructor
public class RefundReceiveAccount {
private String bankCode;
private String accountNumber;
private String holderName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ public class PayOrderCommand {
private PayMethod payMethod;
private PayProvider payProvider;
private final PaymentStatus paymentStatus = PaymentStatus.APPROVED;
private String jsonSubData; // pg사 요구 데이터
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.commerce.platform.core.application.out;

import com.commerce.platform.core.domain.aggreate.CardBinPromotion;

import java.util.List;

public interface CardBinPromotionOutPort {
List<CardBinPromotion> findActivePromotions();
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
package com.commerce.platform.core.application.out;

import com.commerce.platform.core.application.in.dto.PayCancelCommand;
import com.commerce.platform.core.application.in.dto.PayOrderCommand;
import com.commerce.platform.core.application.out.dto.PgPayCancelResponse;
import com.commerce.platform.core.application.out.dto.PgPayResponse;
import com.commerce.platform.core.domain.enums.PayMethod;
import com.commerce.platform.core.domain.enums.PgProvider;

/**
* PG사별 결제를 위한 메소드 정의
*/
public abstract class PgStrategy {

/**
* pg사별 요청에 따라 [Card | Easy | Phone]PayService 구현체 실행한다.
* @param request todo dto
* @return todo 결재응답dto
* 승인
*/
public abstract String process(String request);
public abstract PgPayResponse processApproval(PayOrderCommand command);

/**
* 취소
*/
public abstract PgPayCancelResponse processCancel(PayCancelCommand command);

/**
* PG사명
*/
public abstract PgProvider getPgProvider();

/**
* 결제유형
*/
public abstract PayMethod getPgPayMethod();

/**
* 결제창을 위한 초기화
*/
public abstract Object initPayment();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.commerce.platform.core.application.out.dto;

/**
* pg서 취소 응답 결과 DTO
*/
public record PgPayCancelResponse(
String pgCcTid,
String responseCode, // pg사 응답코드
String responseMessage, // pg사 응답메시지
String cancelReason, // 취소사유
Long cancelAmount, // 취소 금액
boolean isSuccess
) { }
Original file line number Diff line number Diff line change
@@ -1,8 +1,43 @@
package com.commerce.platform.core.application.out.dto;

import com.commerce.platform.core.domain.vo.Money;
import lombok.Builder;

/**
* pg서 승인 응답 결과 DTO
* 결제유형에 따라 card/easyPay/virtualAccount 필드 세팅된다.
*/
@Builder
public record PgPayResponse (
String pgTid,
String responseCode, // pg사 응답코드
String responseMessage, // pg사 응답메시지
boolean isSuccess
) {}
String responseCode, // pg사 응답코드
String responseMessage, // pg사 응답메시지
Money amount,
boolean isSuccess,
Card card, // 카드결제 응답
EasyPay easyPay, // 간편결제 응답
VirtualAccount virtualAccount // 가상계좌 응답
) {
@Builder
public record Card(
String approveNo,
String issuerCode,
String cardType
) {}

@Builder
public record EasyPay(
String provider,
String amount,
String discountAmount
) {}

@Builder
public record VirtualAccount(
String accountType,
String accountNumber,
String bankCode,
String depositorName,
String dueDate
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.commerce.platform.core.domain.aggreate;

import com.commerce.platform.core.domain.enums.PayProvider;
import com.commerce.platform.core.domain.vo.ValidPeriod;
import com.commerce.platform.core.domain.vo.promotion.BasePromotionData;
import com.commerce.platform.infrastructure.persistence.converter.PromotionDataConverter;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;

import java.time.LocalDateTime;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "card_bin_promotion")
@Entity
public class CardBinPromotion {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "card_bin", nullable = false)
private String cardBin;

@Column(name = "card_name", nullable = false)
private String cardName;

@Enumerated(EnumType.STRING)
@Column(name = "pay_provider", nullable = false)
private PayProvider payProvider;

/**
* JSON 프로모션 데이터
* - Converter에서 임시 타입으로 변환
* - 조회 후 PromotionDataPostProcessor에서 PayProvider에 맞게 재변환
*/
@JdbcTypeCode(SqlTypes.JSON)
@Column(name = "promotion_data", nullable = false, columnDefinition = "json")
@Convert(converter = PromotionDataConverter.class)
private BasePromotionData promotionData;

@Column(name = "is_active", nullable = false)
private boolean isActive = true;

@Embedded
private ValidPeriod validPeriod;

@Column(name = "last_updated_at", nullable = false)
private LocalDateTime lastUpdatedAt;

@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;

@Builder
public CardBinPromotion(
String cardBin,
String cardName,
PayProvider payProvider,
BasePromotionData promotionData,
ValidPeriod validPeriod
) {
this.cardBin = cardBin;
this.cardName = cardName;
this.payProvider = payProvider;
this.promotionData = promotionData;
this.validPeriod = validPeriod;
this.isActive = true;
}

@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
this.lastUpdatedAt = LocalDateTime.now();
}

@PreUpdate
protected void onUpdate() {
this.lastUpdatedAt = LocalDateTime.now();
}

public void activate() {
this.isActive = true;
}

public void deactivate() {
this.isActive = false;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.commerce.platform.core.domain.aggreate;

import com.commerce.platform.core.application.in.dto.PayOrderCommand;
import com.commerce.platform.core.application.out.dto.PgPayCancelResponse;
import com.commerce.platform.core.application.out.dto.PgPayResponse;
import com.commerce.platform.core.domain.enums.PayMethod;
import com.commerce.platform.core.domain.enums.PayProvider;
Expand Down Expand Up @@ -96,10 +97,10 @@ public void approved(PgPayResponse pgResponse) {
this.paymentStatus = PaymentStatus.APPROVED;
}

public void canceled(PgPayResponse pgResponse) {
this.pgCancelTid = pgResponse.pgTid();
public void canceled(PgPayCancelResponse cancelResponse) {
this.pgCancelTid = cancelResponse.pgCcTid();

if(!pgResponse.isSuccess()) {
if(!cancelResponse.isSuccess()) {
this.paymentStatus = PaymentStatus.FAILED;
return;
}
Expand Down
Loading
Loading