Ritz Token Initialized
This commit is contained in:
@@ -14,6 +14,9 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import com.rit.canteen.sales.repository.UserRepository;
|
||||
import com.rit.canteen.sales.service.OrderArchiverService;
|
||||
import com.rit.canteen.sales.service.TokenService;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -32,7 +35,13 @@ public class OrderController {
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Autowired
|
||||
private com.rit.canteen.sales.service.OrderArchiverService orderArchiverService;
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private OrderArchiverService orderArchiverService;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
// Use ThreadLocal to safely store conflicts for the current request context
|
||||
private static final ThreadLocal<List<Map<String, Object>>> requestConflicts = new ThreadLocal<>();
|
||||
@@ -163,7 +172,19 @@ public class OrderController {
|
||||
String displayId = String.format("%03d", todaysOrderCount + 1);
|
||||
order.setDisplayOrderId(displayId);
|
||||
|
||||
// 4. Final Save
|
||||
// 4. Token Payment Check
|
||||
if ("RITZ_TOKEN".equals(order.getPaymentMethod())) {
|
||||
try {
|
||||
tokenService.spend(order.getUser().getId(), order.getTotalAmount(), "ORD-" + displayId);
|
||||
} catch (RuntimeException e) {
|
||||
if ("INSUFFICIENT_TOKENS".equals(e.getMessage())) {
|
||||
throw new RuntimeException("INSUFFICIENT_TOKENS");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Final Save
|
||||
Order savedOrder = orderRepository.save(order);
|
||||
|
||||
System.out.println("Placed Order: " + savedOrder.getId() + " -> Display ID: #" + displayId);
|
||||
@@ -188,6 +209,13 @@ public class OrderController {
|
||||
"conflicts", conflicts != null ? conflicts : new ArrayList<>()
|
||||
));
|
||||
}
|
||||
if ("INSUFFICIENT_TOKENS".equals(e.getMessage())) {
|
||||
return ResponseEntity.status(400).body(Map.of(
|
||||
"success", false,
|
||||
"errorType", "TOKEN_ERROR",
|
||||
"message", "Insufficient Ritz Tokens. Please top up your wallet."
|
||||
));
|
||||
}
|
||||
return ResponseEntity.status(500).body(Map.of("success", false, "message", e.getMessage() != null ? e.getMessage() : "Internal Server Error"));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.rit.canteen.sales.controller;
|
||||
|
||||
import com.rit.canteen.sales.model.TokenTransaction;
|
||||
import com.rit.canteen.sales.model.User;
|
||||
import com.rit.canteen.sales.service.TokenService;
|
||||
import com.rit.canteen.sales.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/wallet")
|
||||
public class WalletController {
|
||||
|
||||
@Autowired
|
||||
private com.rit.canteen.sales.repository.UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private com.rit.canteen.sales.service.TokenService tokenService;
|
||||
|
||||
@GetMapping("/balance/{userId}")
|
||||
public ResponseEntity<?> getBalance(@PathVariable Long userId) {
|
||||
try {
|
||||
User user = userRepository.findById(userId).orElse(null);
|
||||
|
||||
if (user == null) return ResponseEntity.notFound().build();
|
||||
return ResponseEntity.ok(Map.of("balance", user.getRitzTokenBalance()));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/transactions/{userId}")
|
||||
public ResponseEntity<List<TokenTransaction>> getTransactions(@PathVariable Long userId) {
|
||||
return ResponseEntity.ok(tokenService.getTransactions(userId));
|
||||
}
|
||||
|
||||
@PostMapping("/topup")
|
||||
public ResponseEntity<?> topUp(@RequestBody Map<String, Object> request) {
|
||||
try {
|
||||
Long userId = Long.valueOf(request.get("userId").toString());
|
||||
BigDecimal amount = new BigDecimal(request.get("amount").toString());
|
||||
String ref = request.getOrDefault("referenceId", "TOPUP-" + System.currentTimeMillis()).toString();
|
||||
|
||||
User updatedUser = tokenService.topUp(userId, amount, ref);
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"success", true,
|
||||
"newBalance", updatedUser.getRitzTokenBalance(),
|
||||
"message", "Successfully added " + amount + " Ritz Tokens"
|
||||
));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.status(500).body(Map.of("error", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,14 +43,16 @@ public class LoginResponse {
|
||||
private String mobileNumber;
|
||||
private String name;
|
||||
private boolean isLoggedIn;
|
||||
private java.math.BigDecimal ritzTokenBalance;
|
||||
|
||||
public UserDto() {}
|
||||
|
||||
public UserDto(Long id, String mobileNumber, String name, boolean isLoggedIn) {
|
||||
public UserDto(Long id, String mobileNumber, String name, boolean isLoggedIn, java.math.BigDecimal ritzTokenBalance) {
|
||||
this.id = id;
|
||||
this.mobileNumber = mobileNumber;
|
||||
this.name = name;
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
this.ritzTokenBalance = ritzTokenBalance;
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
@@ -63,6 +65,9 @@ public class LoginResponse {
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public boolean isLoggedIn() { return isLoggedIn; }
|
||||
public void setLoggedIn(boolean loggedIn) { isLoggedIn = loggedIn; }
|
||||
public void setLoggedIn(boolean loggedIn) { this.isLoggedIn = loggedIn; }
|
||||
|
||||
public java.math.BigDecimal getRitzTokenBalance() { return ritzTokenBalance; }
|
||||
public void setRitzTokenBalance(java.math.BigDecimal ritzTokenBalance) { this.ritzTokenBalance = ritzTokenBalance; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.rit.canteen.sales.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "token_transactions")
|
||||
public class TokenTransaction {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
@Column(nullable = false)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private TransactionType type;
|
||||
|
||||
@Column(length = 255)
|
||||
private String description;
|
||||
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
@Column
|
||||
private String referenceId; // e.g. Order Display ID or Payment ID
|
||||
|
||||
public enum TransactionType {
|
||||
TOPUP,
|
||||
SPEND,
|
||||
REFUND
|
||||
}
|
||||
|
||||
public TokenTransaction() {}
|
||||
|
||||
public TokenTransaction(User user, BigDecimal amount, TransactionType type, String description, String referenceId) {
|
||||
this.user = user;
|
||||
this.amount = amount;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.referenceId = referenceId;
|
||||
this.timestamp = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public User getUser() { return user; }
|
||||
public void setUser(User user) { this.user = user; }
|
||||
|
||||
public BigDecimal getAmount() { return amount; }
|
||||
public void setAmount(BigDecimal amount) { this.amount = amount; }
|
||||
|
||||
public TransactionType getType() { return type; }
|
||||
public void setType(TransactionType type) { this.type = type; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
|
||||
public String getReferenceId() { return referenceId; }
|
||||
public void setReferenceId(String referenceId) { this.referenceId = referenceId; }
|
||||
}
|
||||
@@ -34,6 +34,9 @@ public class User {
|
||||
@Column(nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Column(nullable = false)
|
||||
private java.math.BigDecimal ritzTokenBalance = java.math.BigDecimal.ZERO;
|
||||
|
||||
@Column(nullable = true)
|
||||
private LocalDateTime lastLoginAt;
|
||||
|
||||
@@ -48,6 +51,9 @@ public class User {
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public java.math.BigDecimal getRitzTokenBalance() { return ritzTokenBalance; }
|
||||
public void setRitzTokenBalance(java.math.BigDecimal ritzTokenBalance) { this.ritzTokenBalance = ritzTokenBalance; }
|
||||
|
||||
public String getPinHash() { return pinHash; }
|
||||
public void setPinHash(String pinHash) { this.pinHash = pinHash; }
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.rit.canteen.sales.repository;
|
||||
|
||||
import com.rit.canteen.sales.model.TokenTransaction;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface TokenTransactionRepository extends JpaRepository<TokenTransaction, Long> {
|
||||
List<TokenTransaction> findByUserIdOrderByTimestampDesc(Long userId);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public class DatabaseSeeder implements CommandLineRunner {
|
||||
public void run(String... args) throws Exception {
|
||||
repairStallsSchema();
|
||||
repairOrdersSchema();
|
||||
repairUsersSchema();
|
||||
repairFeedbackSchema();
|
||||
repairLobColumns();
|
||||
seedCategories();
|
||||
@@ -54,6 +55,17 @@ public class DatabaseSeeder implements CommandLineRunner {
|
||||
}
|
||||
}
|
||||
|
||||
private void repairUsersSchema() {
|
||||
System.out.println("Checking schema consistency for 'app_users' table...");
|
||||
try {
|
||||
// Add ritz_token_balance if it doesn't exist
|
||||
jdbcTemplate.execute("ALTER TABLE app_users ADD COLUMN IF NOT EXISTS ritz_token_balance NUMERIC(19, 2) DEFAULT 0 NOT NULL");
|
||||
System.out.println("Users schema consistency confirmed.");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Users schema repair notice: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void repairStallsSchema() {
|
||||
System.out.println("Checking schema consistency for 'stalls' table...");
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.rit.canteen.sales.service;
|
||||
|
||||
import com.rit.canteen.sales.model.TokenTransaction;
|
||||
import com.rit.canteen.sales.model.User;
|
||||
import com.rit.canteen.sales.repository.TokenTransactionRepository;
|
||||
import com.rit.canteen.sales.repository.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class TokenService {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private TokenTransactionRepository transactionRepository;
|
||||
|
||||
public List<TokenTransaction> getTransactions(Long userId) {
|
||||
return transactionRepository.findByUserIdOrderByTimestampDesc(userId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public User topUp(Long userId, BigDecimal amount, String paymentRef) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
BigDecimal currentBalance = user.getRitzTokenBalance() != null ? user.getRitzTokenBalance() : BigDecimal.ZERO;
|
||||
user.setRitzTokenBalance(currentBalance.add(amount));
|
||||
User savedUser = userRepository.save(user);
|
||||
|
||||
TokenTransaction transaction = new TokenTransaction(
|
||||
user,
|
||||
amount,
|
||||
TokenTransaction.TransactionType.TOPUP,
|
||||
"Wallet Top Up via UPI/Card",
|
||||
paymentRef
|
||||
);
|
||||
transactionRepository.save(transaction);
|
||||
|
||||
return savedUser;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void spend(Long userId, BigDecimal amount, String orderRef) {
|
||||
User user = userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
BigDecimal currentBalance = user.getRitzTokenBalance() != null ? user.getRitzTokenBalance() : BigDecimal.ZERO;
|
||||
|
||||
if (currentBalance.compareTo(amount) < 0) {
|
||||
throw new RuntimeException("INSUFFICIENT_TOKENS");
|
||||
}
|
||||
|
||||
user.setRitzTokenBalance(currentBalance.subtract(amount));
|
||||
userRepository.save(user);
|
||||
|
||||
TokenTransaction transaction = new TokenTransaction(
|
||||
user,
|
||||
amount,
|
||||
TokenTransaction.TransactionType.SPEND,
|
||||
"Food Order Payment",
|
||||
orderRef
|
||||
);
|
||||
transactionRepository.save(transaction);
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public class UserService {
|
||||
userRepository.save(user);
|
||||
|
||||
LoginResponse.UserDto userDto = new LoginResponse.UserDto(
|
||||
user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn()
|
||||
user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn(), user.getRitzTokenBalance()
|
||||
);
|
||||
|
||||
return new LoginResponse(true, "Registration successful. You are now logged in.", userDto);
|
||||
@@ -88,7 +88,7 @@ public class UserService {
|
||||
userRepository.save(user);
|
||||
|
||||
LoginResponse.UserDto userDto = new LoginResponse.UserDto(
|
||||
user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn()
|
||||
user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn(), user.getRitzTokenBalance()
|
||||
);
|
||||
|
||||
return new LoginResponse(true, "Login successful.", userDto);
|
||||
@@ -154,7 +154,7 @@ public class UserService {
|
||||
return null;
|
||||
}
|
||||
User user = userOpt.get();
|
||||
return new LoginResponse.UserDto(user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn());
|
||||
return new LoginResponse.UserDto(user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn(), user.getRitzTokenBalance());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,7 +172,8 @@ public class UserService {
|
||||
user.getId(),
|
||||
user.getMobileNumber(),
|
||||
user.getName(),
|
||||
user.isLoggedIn()
|
||||
user.isLoggedIn(),
|
||||
user.getRitzTokenBalance()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -211,7 +212,7 @@ public class UserService {
|
||||
}
|
||||
|
||||
userRepository.save(user);
|
||||
return new LoginResponse.UserDto(user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn());
|
||||
return new LoginResponse.UserDto(user.getId(), user.getMobileNumber(), user.getName(), user.isLoggedIn(), user.getRitzTokenBalance());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user