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
41 changes: 41 additions & 0 deletions src/main/java/com/coinflow/order/service/OrderService.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
import com.coinflow.order.repository.OrderSequenceRepository;
import com.coinflow.trade.domain.Trade;
import com.coinflow.trade.repository.TradeRepository;
import com.coinflow.wallet.domain.LedgerType;
import com.coinflow.wallet.domain.Wallet;
import com.coinflow.wallet.domain.WalletLedger;
import com.coinflow.wallet.repository.WalletLedgerRepository;
import com.coinflow.wallet.repository.WalletRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -40,6 +43,7 @@ public class OrderService {
private final OrderSequenceRepository orderSequenceRepository;
private final WalletRepository walletRepository;
private final TradeRepository tradeRepository;
private final WalletLedgerRepository walletLedgerRepository;
private final MatchingEngine matchingEngine;

@Transactional
Expand Down Expand Up @@ -114,6 +118,12 @@ public CreateOrderResponse createOrder(Long currentUserId, CreateOrderRequest re
);
orderRepository.save(order);

walletLedgerRepository.save(WalletLedger.create(
wallet, LedgerType.ORDER_LOCK,
lockedAmount.negate(), lockedAmount,
order.getId(), null
));

// 11. 매칭 및 정산
List<MatchResult> matchResults = matchingEngine.match(market, order);
List<Trade> trades = settle(market, order, matchResults);
Expand All @@ -133,6 +143,11 @@ public CancelOrderResponse cancelOrder(Long currentUserId, Long orderId) {
Wallet wallet = walletRepository.findByUserIdAndAssetWithLock(currentUserId, order.getLockedAsset())
.orElseThrow(() -> new ApiException(ErrorCode.INSUFFICIENT_BALANCE));
wallet.unlock(releaseAmount);
walletLedgerRepository.save(WalletLedger.create(
wallet, LedgerType.ORDER_CANCEL_RELEASE,
releaseAmount, releaseAmount.negate(),
orderId, null
));

order.cancel();
matchingEngine.cancelOrder(order.getMarketSymbol(), order);
Expand Down Expand Up @@ -189,6 +204,32 @@ private List<Trade> settle(Market market, Order taker, List<MatchResult> matchRe
result.price(), result.quantity(), result.quoteAmount()
);
tradeRepository.save(trade);

Long buyOrderId = result.buyOrderId();
Long sellOrderId = result.sellOrderId();
Long tradeId = trade.getId();

walletLedgerRepository.save(WalletLedger.create(
buyerQuoteWallet, LedgerType.TRADE_BUY_QUOTE_SETTLE,
result.quoteAmount(), result.quoteAmount().negate(),
buyOrderId, tradeId
));
walletLedgerRepository.save(WalletLedger.create(
buyerBaseWallet, LedgerType.TRADE_BUY_BASE_CREDIT,
result.quantity(), BigDecimal.ZERO,
buyOrderId, tradeId
));
walletLedgerRepository.save(WalletLedger.create(
sellerBaseWallet, LedgerType.TRADE_SELL_BASE_SETTLE,
BigDecimal.ZERO, result.quantity().negate(),
sellOrderId, tradeId
));
walletLedgerRepository.save(WalletLedger.create(
sellerQuoteWallet, LedgerType.TRADE_SELL_QUOTE_CREDIT,
result.quoteAmount(), BigDecimal.ZERO,
sellOrderId, tradeId
));

trades.add(trade);
}

Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/coinflow/wallet/api/WalletController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.coinflow.wallet.api;

import com.coinflow.wallet.dto.WalletLedgerResponse;
import com.coinflow.wallet.dto.WalletResponse;
import com.coinflow.wallet.service.WalletService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/wallets")
public class WalletController {

private final WalletService walletService;

@GetMapping
public List<WalletResponse> getWallets(@AuthenticationPrincipal Jwt jwt) {
Long userId = Long.parseLong(jwt.getSubject());
return walletService.getWallets(userId);
}

@GetMapping("/ledgers")
public List<WalletLedgerResponse> getLedgers(
@AuthenticationPrincipal Jwt jwt,
@RequestParam(required = false) String asset
) {
Long userId = Long.parseLong(jwt.getSubject());
return walletService.getLedgers(userId, asset);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/coinflow/wallet/domain/LedgerType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.coinflow.wallet.domain;

public enum LedgerType {
SEED_DEPOSIT,
ORDER_LOCK,
ORDER_CANCEL_RELEASE,
TRADE_BUY_QUOTE_SETTLE,
TRADE_BUY_BASE_CREDIT,
TRADE_SELL_BASE_SETTLE,
TRADE_SELL_QUOTE_CREDIT
}
59 changes: 59 additions & 0 deletions src/main/java/com/coinflow/wallet/domain/WalletLedger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.coinflow.wallet.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.time.LocalDateTime;

@Getter
@Entity
@NoArgsConstructor
@Table(name = "wallet_ledgers")
public class WalletLedger {

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

private Long userId;
private Long walletId;
private String asset;

@Enumerated(EnumType.STRING)
private LedgerType type;

private BigDecimal deltaAvailable;
private BigDecimal deltaLocked;
private BigDecimal availableBalanceAfter;
private BigDecimal lockedBalanceAfter;

private Long orderId;
private Long tradeId;

private LocalDateTime createdAt;

public static WalletLedger create(
Wallet wallet,
LedgerType type,
BigDecimal deltaAvailable,
BigDecimal deltaLocked,
Long orderId,
Long tradeId
) {
WalletLedger ledger = new WalletLedger();
ledger.userId = wallet.getUserId();
ledger.walletId = wallet.getId();
ledger.asset = wallet.getAsset();
ledger.type = type;
ledger.deltaAvailable = deltaAvailable;
ledger.deltaLocked = deltaLocked;
ledger.availableBalanceAfter = wallet.getAvailableBalance();
ledger.lockedBalanceAfter = wallet.getLockedBalance();
ledger.orderId = orderId;
ledger.tradeId = tradeId;
ledger.createdAt = LocalDateTime.now();
return ledger;
}
}
33 changes: 33 additions & 0 deletions src/main/java/com/coinflow/wallet/dto/WalletLedgerResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.coinflow.wallet.dto;

import com.coinflow.wallet.domain.WalletLedger;

import java.time.LocalDateTime;

public record WalletLedgerResponse(
Long ledgerId,
String asset,
String type,
String deltaAvailable,
String deltaLocked,
String availableBalanceAfter,
String lockedBalanceAfter,
Long orderId,
Long tradeId,
LocalDateTime createdAt
) {
public static WalletLedgerResponse from(WalletLedger ledger) {
return new WalletLedgerResponse(
ledger.getId(),
ledger.getAsset(),
ledger.getType().name(),
ledger.getDeltaAvailable().toPlainString(),
ledger.getDeltaLocked().toPlainString(),
ledger.getAvailableBalanceAfter().toPlainString(),
ledger.getLockedBalanceAfter().toPlainString(),
ledger.getOrderId(),
ledger.getTradeId(),
ledger.getCreatedAt()
);
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/coinflow/wallet/dto/WalletResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.coinflow.wallet.dto;

import com.coinflow.wallet.domain.Wallet;

public record WalletResponse(
Long walletId,
String asset,
String availableBalance,
String lockedBalance
) {
public static WalletResponse from(Wallet wallet) {
return new WalletResponse(
wallet.getId(),
wallet.getAsset(),
wallet.getAvailableBalance().toPlainString(),
wallet.getLockedBalance().toPlainString()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.coinflow.wallet.repository;

import com.coinflow.wallet.domain.WalletLedger;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface WalletLedgerRepository extends JpaRepository<WalletLedger, Long> {

List<WalletLedger> findAllByUserIdOrderByCreatedAtDesc(Long userId);

List<WalletLedger> findAllByUserIdAndAssetOrderByCreatedAtDesc(Long userId, String asset);
}
35 changes: 35 additions & 0 deletions src/main/java/com/coinflow/wallet/service/WalletService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.coinflow.wallet.service;

import com.coinflow.wallet.dto.WalletLedgerResponse;
import com.coinflow.wallet.dto.WalletResponse;
import com.coinflow.wallet.repository.WalletLedgerRepository;
import com.coinflow.wallet.repository.WalletRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class WalletService {

private final WalletRepository walletRepository;
private final WalletLedgerRepository walletLedgerRepository;

@Transactional(readOnly = true)
public List<WalletResponse> getWallets(Long userId) {
return walletRepository.findAllByUserId(userId)
.stream()
.map(WalletResponse::from)
.toList();
}

@Transactional(readOnly = true)
public List<WalletLedgerResponse> getLedgers(Long userId, String asset) {
var ledgers = (asset != null)
? walletLedgerRepository.findAllByUserIdAndAssetOrderByCreatedAtDesc(userId, asset)
: walletLedgerRepository.findAllByUserIdOrderByCreatedAtDesc(userId);
return ledgers.stream().map(WalletLedgerResponse::from).toList();
}
}
Loading
Loading