Merge pull request 'feat(email): add deposit confirmation email' (!181) from feat/mails into main
Reviewed-on: #181 Reviewed-by: Jan-Marlon Leibl <jleibl@proton.me>
This commit is contained in:
		
				commit
				
					
						0cde085102
					
				
			
		
					 5 changed files with 172 additions and 4 deletions
				
			
		| 
						 | 
					@ -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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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")) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										136
									
								
								backend/src/main/resources/templates/email/deposit.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								backend/src/main/resources/templates/email/deposit.html
									
										
									
									
									
										Normal 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>
 | 
				
			||||||
		Reference in a new issue