Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 결제 승인 기능 구현 #154

Merged
merged 50 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
67b97c2
feat: 결제 엔티티 생성
kmebin Nov 16, 2023
681919f
feat: 벌레 상품 구매 API 구현
kmebin Nov 16, 2023
cb5b7ed
Merge branch 'develop' into feature/#55-purchase-bug-product
kmebin Nov 17, 2023
03e02ed
test: 벌레 상품 구매 통합 테스트
kmebin Nov 17, 2023
d77ed1d
test: 벌레 상품 구매 서비스 테스트
kmebin Nov 17, 2023
8612c77
test: 결제 쿠폰 적용 테스트
kmebin Nov 17, 2023
72776a4
test: 주문 생성 및 금액 할인 테스트
kmebin Nov 17, 2023
b97ce47
test: 벌레 사용 및 증가 로직 검증 방식 수정
kmebin Nov 17, 2023
be0a1d3
chore: config 업데이트
kmebin Nov 17, 2023
07388bd
fix: 상품 구매 Response에 주문 id 제거
kmebin Nov 18, 2023
663257f
feat: 상품 구매 Response에 결제 id 추가
kmebin Nov 18, 2023
afb2bde
fix: Transactional 적용
kmebin Nov 18, 2023
938bae3
style: 메서드 네이밍 수정
kmebin Nov 18, 2023
c9c54f4
feat: 결제 요청 전 대기 상태 추가
kmebin Nov 18, 2023
0dfa5f4
feat: 결제 요청 API 구현
kmebin Nov 18, 2023
287225e
fix: Valid 어노테이션 추가
kmebin Nov 18, 2023
b084159
test: 결제 요청 통합 테스트
kmebin Nov 18, 2023
b23feef
test: 결제 요청 서비스 테스트
kmebin Nov 18, 2023
53e29f4
test: 결제/주문 유닛 테스트
kmebin Nov 18, 2023
fe4b4fe
style: 줄바꿈 수정
kmebin Nov 18, 2023
93255fd
feat: order_id 컬럼 인덱스 설정
kmebin Nov 18, 2023
70937bf
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 20, 2023
b542c20
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 21, 2023
23a1335
chore: webflux 의존성 추가
kmebin Nov 21, 2023
37bdbe4
feat: 토스 결제 위젯 승인 API 연동
kmebin Nov 21, 2023
aa9fa42
feat: 결제 승인 API 구현
kmebin Nov 21, 2023
bfbdc47
feat: 결제 테이블에 couponWalletId 컬럼 추가
kmebin Nov 21, 2023
0615f24
test: 결제 승인 통합 테스트
kmebin Nov 21, 2023
7e27ac9
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 21, 2023
2e1ce00
feat: 벌레 상품 구매 시 couponWallet 검증 로직 적용
kmebin Nov 22, 2023
926b474
fix: couponWalletId를 받도록 수정
kmebin Nov 22, 2023
5ad93f6
test: couponWallet 적용 테스트
kmebin Nov 22, 2023
83e3e1b
chore: 불필요한 fixture 제거
kmebin Nov 22, 2023
ba88d10
Merge branch 'feature/#140-purchase-bug-product' into feature/#111-ap…
kmebin Nov 22, 2023
7415f0b
feat: 결제 승인 시 쿠폰 차감 및 벌레 충전 로직 추가
kmebin Nov 22, 2023
56c0145
fix: 쿠폰이 적용된 경우 분기 처리
kmebin Nov 22, 2023
5b70707
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 23, 2023
c1d8b0b
chore: config 업데이트
kmebin Nov 23, 2023
2b863d7
test: 결제 승인 컨트롤러 통합 테스트
kmebin Nov 24, 2023
2c0c0b5
test: 결제 승인 서비스 테스트
kmebin Nov 24, 2023
54da7df
chore: MockWebServer 의존성 추가
kmebin Nov 26, 2023
1c8c70a
test: 토스 결제 승인 API 테스트
kmebin Nov 26, 2023
011bb0e
fix: checkStyle 오류 수정
kmebin Nov 26, 2023
574fa34
chore: config 업데이트
kmebin Nov 26, 2023
bde153c
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 26, 2023
abe23c7
refactor: 결제 테이블 coupon_id 컬럼을 discount_amount로 변경
kmebin Nov 26, 2023
96ca221
refactor: 공통 메서드 분리
kmebin Nov 26, 2023
ec2043d
feat: 벌레 충전 시 벌레 내역 저장 로직 추가
kmebin Nov 26, 2023
92bb3db
style: 중복 메서드 제거
kmebin Nov 26, 2023
6842bf3
Merge branch 'develop' into feature/#111-approve-payment
kmebin Nov 26, 2023
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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0'

// Querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
Expand Down Expand Up @@ -92,6 +93,9 @@ dependencies {
// S3
implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2")
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3'

// webflux
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}

tasks.named('test') {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/moabam/api/application/bug/BugMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@ public static BugHistory toUseBugHistory(Long memberId, BugType bugType, int qua
.quantity(quantity)
.build();
}

public static BugHistory toChargeBugHistory(Long memberId, int quantity) {
return BugHistory.builder()
.memberId(memberId)
.bugType(BugType.GOLDEN)
.actionType(BugActionType.CHARGE)
.quantity(quantity)
.build();
}
}
16 changes: 13 additions & 3 deletions src/main/java/com/moabam/api/application/bug/BugService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import com.moabam.api.application.member.MemberService;
import com.moabam.api.application.payment.PaymentMapper;
import com.moabam.api.application.product.ProductMapper;
import com.moabam.api.domain.bug.Bug;
import com.moabam.api.domain.bug.repository.BugHistoryRepository;
import com.moabam.api.domain.coupon.Coupon;
import com.moabam.api.domain.member.Member;
import com.moabam.api.domain.payment.Payment;
import com.moabam.api.domain.payment.repository.PaymentRepository;
import com.moabam.api.domain.product.Product;
Expand All @@ -34,13 +35,14 @@ public class BugService {

private final MemberService memberService;
private final CouponService couponService;
private final BugHistoryRepository bugHistoryRepository;
private final ProductRepository productRepository;
private final PaymentRepository paymentRepository;

public BugResponse getBug(Long memberId) {
Member member = memberService.getById(memberId);
Bug bug = memberService.getById(memberId).getBug();

return BugMapper.toBugResponse(member.getBug());
return BugMapper.toBugResponse(bug);
}

public ProductsResponse getBugProducts() {
Expand All @@ -63,6 +65,14 @@ public PurchaseProductResponse purchaseBugProduct(Long memberId, Long productId,
return ProductMapper.toPurchaseProductResponse(payment);
}

@Transactional
public void charge(Long memberId, Product bugProduct) {
Bug bug = memberService.getById(memberId).getBug();

bug.charge(bugProduct.getQuantity());
bugHistoryRepository.save(BugMapper.toChargeBugHistory(memberId, bugProduct.getQuantity()));
}

private Product getById(Long productId) {
return productRepository.findById(productId)
.orElseThrow(() -> new NotFoundException(PRODUCT_NOT_FOUND));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.moabam.api.domain.coupon.CouponWallet;
import com.moabam.api.domain.coupon.repository.CouponRepository;
import com.moabam.api.domain.coupon.repository.CouponSearchRepository;
import com.moabam.api.domain.coupon.repository.CouponWalletRepository;
import com.moabam.api.domain.coupon.repository.CouponWalletSearchRepository;
import com.moabam.api.domain.member.Role;
import com.moabam.api.dto.coupon.CouponResponse;
Expand All @@ -32,9 +33,9 @@ public class CouponService {

private final ClockHolder clockHolder;
private final CouponManageService couponManageService;

private final CouponRepository couponRepository;
private final CouponSearchRepository couponSearchRepository;
private final CouponWalletRepository couponWalletRepository;
private final CouponWalletSearchRepository couponWalletSearchRepository;

@Transactional
Expand All @@ -57,6 +58,12 @@ public void delete(AuthMember admin, Long couponId) {
couponManageService.deleteCouponManage(coupon.getName());
}

@Transactional
public void use(Long memberId, Long couponWalletId) {
Coupon coupon = getByWalletIdAndMemberId(couponWalletId, memberId);
couponRepository.delete(coupon);
}

public CouponResponse getById(Long couponId) {
Coupon coupon = couponRepository.findById(couponId)
.orElseThrow(() -> new NotFoundException(ErrorMessage.NOT_FOUND_COUPON));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static Payment toPayment(Long memberId, Product product) {
.memberId(memberId)
.product(product)
.order(order)
.amount(product.getPrice())
.totalAmount(product.getPrice())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.moabam.api.application.bug.BugService;
import com.moabam.api.application.coupon.CouponService;
import com.moabam.api.domain.payment.Payment;
import com.moabam.api.domain.payment.repository.PaymentRepository;
import com.moabam.api.domain.payment.repository.PaymentSearchRepository;
import com.moabam.api.dto.payment.ConfirmPaymentRequest;
import com.moabam.api.dto.payment.ConfirmTossPaymentResponse;
import com.moabam.api.dto.payment.PaymentRequest;
import com.moabam.api.infrastructure.payment.TossPaymentMapper;
import com.moabam.api.infrastructure.payment.TossPaymentService;
import com.moabam.global.error.exception.MoabamException;
import com.moabam.global.error.exception.NotFoundException;

import lombok.RequiredArgsConstructor;
Expand All @@ -17,7 +25,11 @@
@RequiredArgsConstructor
public class PaymentService {

private final BugService bugService;
private final CouponService couponService;
private final TossPaymentService tossPaymentService;
private final PaymentRepository paymentRepository;
private final PaymentSearchRepository paymentSearchRepository;

@Transactional
public void request(Long memberId, Long paymentId, PaymentRequest request) {
Expand All @@ -26,8 +38,33 @@ public void request(Long memberId, Long paymentId, PaymentRequest request) {
payment.request(request.orderId());
}

@Transactional
public void confirm(Long memberId, ConfirmPaymentRequest request) {
Payment payment = getByOrderId(request.orderId());
payment.validateInfo(memberId, request.amount());

try {
ConfirmTossPaymentResponse response = tossPaymentService.confirm(
TossPaymentMapper.toConfirmRequest(request.paymentKey(), request.orderId(), request.amount())
);
payment.confirm(response.paymentKey(), response.approvedAt());

if (payment.isCouponApplied()) {
couponService.use(memberId, payment.getCouponWalletId());
}
bugService.charge(memberId, payment.getProduct());
} catch (MoabamException exception) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: 결제API를 먼저 호출한 거면 트랜잭션 분리가 가능하다고 느껴졌는데, 분리하지 않고 한 번에 넣으신 이유가 있을까요?

payment.fail(request.paymentKey());
}
}

private Payment getById(Long paymentId) {
return paymentRepository.findById(paymentId)
.orElseThrow(() -> new NotFoundException(PAYMENT_NOT_FOUND));
}

private Payment getByOrderId(String orderId) {
return paymentSearchRepository.findByOrderId(orderId)
.orElseThrow(() -> new NotFoundException(PAYMENT_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static PurchaseProductResponse toPurchaseProductResponse(Payment payment)
return PurchaseProductResponse.builder()
.paymentId(payment.getId())
.orderName(payment.getOrder().getName())
.price(payment.getAmount())
.price(payment.getTotalAmount())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.moabam.api.application.room;

import static com.moabam.global.error.model.ErrorMessage.DUPLICATED_DAILY_MEMBER_CERTIFICATION;
import static com.moabam.global.error.model.ErrorMessage.INVALID_CERTIFY_TIME;
import static com.moabam.global.error.model.ErrorMessage.PARTICIPANT_NOT_FOUND;
import static com.moabam.global.error.model.ErrorMessage.ROUTINE_NOT_FOUND;
import static com.moabam.global.error.model.ErrorMessage.*;

import java.time.LocalDate;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -81,7 +78,7 @@ public void certifyRoom(Long memberId, Long roomId, List<String> imageUrls) {
return;
}

member.getBug().increaseBug(bugType, roomLevel);
member.getBug().increase(bugType, roomLevel);
}

public boolean existsMemberCertification(Long memberId, Long roomId, LocalDate date) {
Expand Down Expand Up @@ -175,6 +172,6 @@ private void provideBugToCompletedMembers(BugType bugType, List<DailyMemberCerti
.toList();

memberService.getRoomMembers(memberIds)
.forEach(completedMember -> completedMember.getBug().increaseBug(bugType, expAppliedRoomLevel));
.forEach(completedMember -> completedMember.getBug().increase(bugType, expAppliedRoomLevel));
}
}
10 changes: 7 additions & 3 deletions src/main/java/com/moabam/api/domain/bug/Bug.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private int validateBugCount(int bug) {
public void use(BugType bugType, int price) {
int currentBug = getBug(bugType);
validateEnoughBug(currentBug, price);
decreaseBug(bugType, price);
decrease(bugType, price);
}

private int getBug(BugType bugType) {
Expand All @@ -65,19 +65,23 @@ private void validateEnoughBug(int currentBug, int price) {
}
}

private void decreaseBug(BugType bugType, int bug) {
private void decrease(BugType bugType, int bug) {
switch (bugType) {
case MORNING -> this.morningBug -= bug;
case NIGHT -> this.nightBug -= bug;
case GOLDEN -> this.goldenBug -= bug;
}
}

public void increaseBug(BugType bugType, int bug) {
public void increase(BugType bugType, int bug) {
switch (bugType) {
case MORNING -> this.morningBug += bug;
case NIGHT -> this.nightBug += bug;
case GOLDEN -> this.goldenBug += bug;
}
}

public void charge(int quantity) {
this.goldenBug += quantity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public class BugHistorySearchRepository {
private final JPAQueryFactory jpaQueryFactory;

public List<BugHistory> find(Long memberId, BugActionType actionType, LocalDateTime dateTime) {
return jpaQueryFactory
.selectFrom(bugHistory)
return jpaQueryFactory.selectFrom(bugHistory)
.where(
DynamicQuery.generateEq(memberId, bugHistory.memberId::eq),
DynamicQuery.generateEq(actionType, bugHistory.actionType::eq),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,23 @@ public class InventorySearchRepository {
private final JPAQueryFactory jpaQueryFactory;

public Optional<Inventory> findOne(Long memberId, Long itemId) {
return Optional.ofNullable(
jpaQueryFactory.selectFrom(inventory)
.where(
DynamicQuery.generateEq(memberId, inventory.memberId::eq),
DynamicQuery.generateEq(itemId, inventory.item.id::eq))
.fetchOne()
return Optional.ofNullable(jpaQueryFactory
.selectFrom(inventory)
.where(
DynamicQuery.generateEq(memberId, inventory.memberId::eq),
DynamicQuery.generateEq(itemId, inventory.item.id::eq))
.fetchOne()
);
}

public Optional<Inventory> findDefault(Long memberId, ItemType type) {
return Optional.ofNullable(
jpaQueryFactory.selectFrom(inventory)
.where(
DynamicQuery.generateEq(memberId, inventory.memberId::eq),
DynamicQuery.generateEq(type, inventory.item.type::eq),
inventory.isDefault.isTrue())
.fetchOne()
return Optional.ofNullable(jpaQueryFactory
.selectFrom(inventory)
.where(
DynamicQuery.generateEq(memberId, inventory.memberId::eq),
DynamicQuery.generateEq(type, inventory.item.type::eq),
inventory.isDefault.isTrue())
.fetchOne()
);
}

Expand Down
49 changes: 38 additions & 11 deletions src/main/java/com/moabam/api/domain/payment/Payment.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.moabam.api.domain.payment;

import static com.moabam.global.error.model.ErrorMessage.*;
import static java.lang.Math.*;
import static java.util.Objects.*;

import java.time.LocalDateTime;
Expand Down Expand Up @@ -53,18 +52,17 @@ public class Payment {
@JoinColumn(name = "product_id", updatable = false, nullable = false)
private Product product;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "coupon_id")
private Coupon coupon;

@Column(name = "coupon_wallet_id")
private Long couponWalletId;

@Embedded
private Order order;

@Column(name = "amount", nullable = false)
private int amount;
@Column(name = "total_amount", nullable = false)
private int totalAmount;

@Column(name = "discount_amount", nullable = false)
private int discountAmount;

@Column(name = "payment_key")
private String paymentKey;
Expand All @@ -84,11 +82,14 @@ public class Payment {
private LocalDateTime approvedAt;

@Builder
public Payment(Long memberId, Product product, Order order, int amount, PaymentStatus status) {
public Payment(Long memberId, Product product, Long couponWalletId, Order order, int totalAmount,
int discountAmount, PaymentStatus status) {
this.memberId = requireNonNull(memberId);
this.product = requireNonNull(product);
this.couponWalletId = couponWalletId;
this.order = requireNonNull(order);
this.amount = validateAmount(amount);
this.totalAmount = validateAmount(totalAmount);
this.discountAmount = validateAmount(discountAmount);
this.status = requireNonNullElse(status, PaymentStatus.READY);
}

Expand All @@ -100,20 +101,46 @@ private int validateAmount(int amount) {
return amount;
}

public void validateInfo(Long memberId, int amount) {
validateByMember(memberId);
validateByTotalAmount(amount);
}

public void validateByMember(Long memberId) {
if (!this.memberId.equals(memberId)) {
throw new BadRequestException(INVALID_MEMBER_PAYMENT);
}
}

private void validateByTotalAmount(int amount) {
if (this.totalAmount != amount) {
throw new BadRequestException(INVALID_PAYMENT_INFO);
}
}

public boolean isCouponApplied() {
return !isNull(this.couponWalletId);
}

public void applyCoupon(Coupon coupon, Long couponWalletId) {
this.coupon = coupon;
this.couponWalletId = couponWalletId;
this.amount = max(MIN_AMOUNT, this.amount - coupon.getPoint());
this.discountAmount = coupon.getPoint();
this.totalAmount = Math.max(MIN_AMOUNT, this.totalAmount - coupon.getPoint());
}

public void request(String orderId) {
this.order.updateId(orderId);
this.requestedAt = LocalDateTime.now();
}

public void confirm(String paymentKey, LocalDateTime approvedAt) {
this.paymentKey = paymentKey;
this.approvedAt = approvedAt;
this.status = PaymentStatus.DONE;
}

public void fail(String paymentKey) {
this.paymentKey = paymentKey;
this.status = PaymentStatus.ABORTED;
}
}
Loading