Merge pull request 'feat(email): add deposit confirmation email' (!181) from feat/mails into main
All checks were successful
Release / Release (push) Successful in 1m6s
Release / Build Frontend Image (push) Successful in 30s
Release / Build Backend Image (push) Successful in 1m3s

Reviewed-on: #181
Reviewed-by: Jan-Marlon Leibl <jleibl@proton.me>
This commit is contained in:
Jan-Marlon Leibl 2025-05-14 07:27:20 +00:00
commit 0cde085102
No known key found for this signature in database
GPG key ID: 944223E4D46B7412
5 changed files with 172 additions and 4 deletions

View file

@ -3,10 +3,13 @@ package de.szut.casino.deposit;
import com.stripe.exception.StripeException; import com.stripe.exception.StripeException;
import com.stripe.model.checkout.Session; import com.stripe.model.checkout.Session;
import com.stripe.param.checkout.SessionRetrieveParams; import com.stripe.param.checkout.SessionRetrieveParams;
import de.szut.casino.security.service.EmailService;
import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserEntity;
import de.szut.casino.user.UserRepository; import de.szut.casino.user.UserRepository;
import jakarta.mail.MessagingException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Optional; import java.util.Optional;
@ -14,10 +17,12 @@ import java.util.Optional;
public class TransactionService { public class TransactionService {
private final TransactionRepository transactionRepository; private final TransactionRepository transactionRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final EmailService emailService;
public TransactionService(TransactionRepository transactionRepository, UserRepository userRepository) { public TransactionService(TransactionRepository transactionRepository, UserRepository userRepository, EmailService emailService) {
this.transactionRepository = transactionRepository; this.transactionRepository = transactionRepository;
this.userRepository = userRepository; this.userRepository = userRepository;
this.emailService = emailService;
} }
public void createTransaction( public void createTransaction(
@ -34,7 +39,7 @@ public class TransactionService {
transactionRepository.save(transaction); transactionRepository.save(transaction);
} }
public void fulfillCheckout(String sessionID) throws StripeException { public void fulfillCheckout(String sessionID) throws StripeException, MessagingException, IOException {
SessionRetrieveParams params = SessionRetrieveParams.builder() SessionRetrieveParams params = SessionRetrieveParams.builder()
.addExpand("line_items") .addExpand("line_items")
.build(); .build();
@ -60,5 +65,6 @@ public class TransactionService {
userRepository.save(user); userRepository.save(user);
transactionRepository.save(transaction); transactionRepository.save(transaction);
emailService.sendDepositEmail(transaction);
} }
} }

View file

@ -6,6 +6,7 @@ import com.stripe.model.Event;
import com.stripe.model.checkout.Session; import com.stripe.model.checkout.Session;
import com.stripe.net.Webhook; import com.stripe.net.Webhook;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.mail.MessagingException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
@RestController @RestController
@ -38,7 +40,7 @@ public class WebhookController {
} }
@PostMapping("/webhook") @PostMapping("/webhook")
public ResponseEntity<String> webhook(@RequestBody String payload, @RequestHeader("Stripe-Signature") String sigHeader) throws StripeException { public ResponseEntity<String> webhook(@RequestBody String payload, @RequestHeader("Stripe-Signature") String sigHeader) throws StripeException, MessagingException, IOException {
Event event = Webhook.constructEvent(payload, sigHeader, webhookSecret); Event event = Webhook.constructEvent(payload, sigHeader, webhookSecret);
if (Objects.equals(event.getType(), "checkout.session.completed") || Objects.equals(event.getType(), "checkout.session.async_payment_succeeded")) { if (Objects.equals(event.getType(), "checkout.session.completed") || Objects.equals(event.getType(), "checkout.session.async_payment_succeeded")) {

View file

@ -1,5 +1,6 @@
package de.szut.casino.security.service; package de.szut.casino.security.service;
import de.szut.casino.deposit.TransactionEntity;
import de.szut.casino.user.UserEntity; import de.szut.casino.user.UserEntity;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
@ -14,6 +15,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List;
@Service @Service
public class EmailService { public class EmailService {
@ -43,13 +45,31 @@ public class EmailService {
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(mailConfig.fromAddress); helper.setFrom(mailConfig.fromAddress);
helper.setTo(user.getEmail()); helper.setTo(user.getEmailAddress());
helper.setSubject("Willkommen bei Trustworthy Casino©"); helper.setSubject("Willkommen bei Trustworthy Casino©");
helper.setText(htmlContent, true); helper.setText(htmlContent, true);
mailSender.send(message); mailSender.send(message);
} }
public void sendDepositEmail(TransactionEntity transaction) throws IOException, MessagingException {
String template = loadTemplate("email/deposit.html");
String htmlContent = template
.replace("${username}", transaction.getUser().getUsername())
.replace("${amount}", String.valueOf(transaction.getAmount()))
.replace("${feUrl}", feUrl);
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(mailConfig.fromAddress);
helper.setTo(transaction.getUser().getEmailAddress());
helper.setSubject("Einzahlung über ${amount}€ Erfolgreich".replace("${amount}", String.valueOf(transaction.getAmount())));
helper.setText(htmlContent, true);
mailSender.send(message);
}
private String loadTemplate(String templatePath) throws IOException { private String loadTemplate(String templatePath) throws IOException {
ClassPathResource resource = new ClassPathResource("templates/" + templatePath); ClassPathResource resource = new ClassPathResource("templates/" + templatePath);
try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) { try (Reader reader = new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8)) {

View file

@ -64,4 +64,8 @@ public class UserEntity {
this.balance = this.balance.subtract(amountToSubtract); this.balance = this.balance.subtract(amountToSubtract);
} }
public String getEmailAddress() {
return "${name} <${email}>".replace("${name}", this.username).replace("${email}", this.email);
}
} }

View file

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Einzahlung bestätigt - Trustworthy Casino©</title>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
background-color: #f8fafc;
color: #64748b;
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.container {
background-color: #0a1219;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #1a2835;
padding: 20px;
text-align: center;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 28px;
}
.content {
background-color: #121e27;
padding: 30px;
color: #ffffff;
}
.footer {
background-color: #1a2835;
color: #94a3b8;
padding: 20px;
text-align: center;
font-size: 0.8em;
}
.button {
display: inline-block;
background-color: #10b981;
color: #ffffff;
padding: 12px 24px;
margin: 20px 0;
text-decoration: none;
border-radius: 6px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s;
}
.button:hover {
background-color: #059669;
}
h2 {
color: #ffffff;
padding-bottom: 10px;
display: inline-block;
}
ul {
padding-left: 20px;
margin: 20px 0;
}
li {
margin-bottom: 8px;
color: #94a3b8;
}
li::marker {
color: #34d399;
}
.highlight {
color: #10b981;
font-weight: bold;
}
.divider {
height: 1px;
background-color: #1a2835;
margin: 20px 0;
}
p {
margin: 16px 0;
}
.amount-box {
background-color: #1a2835;
border-radius: 6px;
padding: 15px;
text-align: center;
margin: 20px 0;
}
.amount-value {
font-size: 28px;
font-weight: bold;
color: #10b981;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Trustworthy Casino</h1>
</div>
<div class="content">
<h2>Hallo <span class="highlight">${username}</span>,</h2>
<p>vielen Dank für Ihre Einzahlung bei Trustworthy Casino. Wir bestätigen den Eingang Ihres Guthabens.</p>
<div class="amount-box">
<p>Eingezahlter Betrag</p>
<div class="amount-value">${amount}€</div>
</div>
<div class="divider"></div>
<p>Ihr Guthaben wurde Ihrem Konto sofort gutgeschrieben und steht ab sofort zum Spielen zur Verfügung.</p>
<div style="text-align: center;">
<a href="${feUrl}/games" class="button">Jetzt Spielen</a>
</div>
<p>Bei Fragen zu Ihrer Einzahlung kontaktieren Sie bitte unseren Kundenservice.</p>
<p>Mit freundlichen Grüßen,<br>
Ihr <span style="color: #10b981;">Trustworthy Casino</span> Team</p>
</div>
<div class="footer">
<p>2025 Trustworthy Casino - Alle Rechte vorbehalten</p>
<p>Diese E-Mail wurde automatisch generiert. Bitte antworten Sie nicht darauf.</p>
</div>
</div>
</body>
</html>