diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index f10394a..565e524 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -24,6 +24,7 @@ repositories { } dependencies { + implementation("com.stripe:stripe-java:20.79.0") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-web") compileOnly("org.projectlombok:lombok") diff --git a/backend/src/main/java/de/szut/casino/deposit/DepositController.java b/backend/src/main/java/de/szut/casino/deposit/DepositController.java new file mode 100644 index 0000000..0e1ade5 --- /dev/null +++ b/backend/src/main/java/de/szut/casino/deposit/DepositController.java @@ -0,0 +1,50 @@ +package de.szut.casino.deposit; + +import com.stripe.Stripe; +import com.stripe.exception.StripeException; +import com.stripe.model.checkout.Session; +import com.stripe.param.InvoiceItemCreateParams; +import com.stripe.param.PriceCreateParams; +import com.stripe.param.checkout.SessionCreateParams; +import de.szut.casino.deposit.dto.AmountDto; +import de.szut.casino.deposit.dto.SessionIdDto; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.cassandra.CassandraProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DepositController { + + @Value("${stripe.secret.key}") + private String stripeKey; + + @PostMapping("/deposit/checkout") + public ResponseEntity checkout( + @RequestBody @Valid AmountDto amountDto, + @RequestHeader("Origin") String origin + ) throws StripeException { + Stripe.apiKey = stripeKey; + + SessionCreateParams params = SessionCreateParams.builder() + .addLineItem(SessionCreateParams.LineItem.builder() + .setAmount((long) amountDto.getAmount() * 100) + .setCurrency("EUR") + .setQuantity(1L) + .setName("Einzahlung") + .build()) + .setSuccessUrl(origin+"/deposit/success") + .setMode(SessionCreateParams.Mode.PAYMENT) + .build(); + + Session session = Session.create(params); + + return ResponseEntity.ok(new SessionIdDto(session.getId())); + } +} + diff --git a/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java b/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java new file mode 100644 index 0000000..1f1708e --- /dev/null +++ b/backend/src/main/java/de/szut/casino/deposit/dto/AmountDto.java @@ -0,0 +1,17 @@ +package de.szut.casino.deposit.dto; + +import jakarta.validation.constraints.Min; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class AmountDto { + @Min(50) + private double amount; +} + diff --git a/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java b/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java new file mode 100644 index 0000000..b3de1bc --- /dev/null +++ b/backend/src/main/java/de/szut/casino/deposit/dto/SessionIdDto.java @@ -0,0 +1,15 @@ +package de.szut.casino.deposit.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SessionIdDto { + private String sessionId; +} + diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index d668f7a..268279f 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,9 +1,9 @@ -spring.datasource.url=jdbc:postgresql://${DB_HOST:-localhost}:5432/postgresdb +spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:5432/postgresdb spring.datasource.username=postgres_user spring.datasource.password=postgres_pass server.port=8080 spring.jpa.hibernate.ddl-auto=create-drop - +stripe.secret.key=${STRIPE_SECRET_KEY:sk_test_51QrePYIvCfqz7ANgqam8rEwWcMeKiLOof3j6SCMgu2sl4sESP45DJxca16mWcYo1sQaiBv32CMR6Z4AAAGQPCJo300ubuZKO8I} spring.application.name=lf12_starter #client registration configuration diff --git a/frontend/bun.lock b/frontend/bun.lock index dd78f81..2154b1a 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -12,6 +12,7 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", + "@stripe/stripe-js": "^5.6.0", "@tailwindcss/postcss": "^4.0.3", "keycloak-angular": "^16.0.1", "keycloak-js": "^25.0.5", @@ -530,6 +531,8 @@ "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], + "@stripe/stripe-js": ["@stripe/stripe-js@5.6.0", "", {}, "sha512-w8CEY73X/7tw2KKlL3iOk679V9bWseE4GzNz3zlaYxcTjmcmWOathRb0emgo/QQ3eoNzmq68+2Y2gxluAv3xGw=="], + "@tailwindcss/node": ["@tailwindcss/node@4.0.3", "", { "dependencies": { "enhanced-resolve": "^5.18.0", "jiti": "^2.4.2", "tailwindcss": "4.0.3" } }, "sha512-QsVJokOl0pJ4AbJV33D2npvLcHGPWi5MOSZtrtE0GT3tSx+3D0JE2lokLA8yHS1x3oCY/3IyRyy7XX6tmzid7A=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.3", "@tailwindcss/oxide-darwin-arm64": "4.0.3", "@tailwindcss/oxide-darwin-x64": "4.0.3", "@tailwindcss/oxide-freebsd-x64": "4.0.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.3", "@tailwindcss/oxide-linux-arm64-musl": "4.0.3", "@tailwindcss/oxide-linux-x64-gnu": "4.0.3", "@tailwindcss/oxide-linux-x64-musl": "4.0.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.3", "@tailwindcss/oxide-win32-x64-msvc": "4.0.3" } }, "sha512-FFcp3VNvRjjmFA39ORM27g2mbflMQljhvM7gxBAujHxUy4LXlKa6yMF9wbHdTbPqTONiCyyOYxccvJyVyI/XBg=="], diff --git a/frontend/package.json b/frontend/package.json index 2a1f257..6d79510 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@angular/platform-browser": "^18.2.0", "@angular/platform-browser-dynamic": "^18.2.0", "@angular/router": "^18.2.0", + "@stripe/stripe-js": "^5.6.0", "@tailwindcss/postcss": "^4.0.3", "keycloak-angular": "^16.0.1", "keycloak-js": "^25.0.5", diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 73ed20c..b58c796 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -2,6 +2,7 @@ import { Routes } from '@angular/router'; import { LandingComponent } from './feature/landing/landing.component'; import { HomeComponent } from './feature/home/home.component'; import { authGuard } from './auth.guard'; +import { DepositComponent } from './deposit/deposit.component'; export const routes: Routes = [ { @@ -13,4 +14,9 @@ export const routes: Routes = [ component: HomeComponent, canActivate: [authGuard], }, + { + path: 'deposit', + component: DepositComponent, + canActivate: [authGuard], + }, ]; diff --git a/frontend/src/app/deposit/deposit.component.css b/frontend/src/app/deposit/deposit.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/deposit/deposit.component.html b/frontend/src/app/deposit/deposit.component.html new file mode 100644 index 0000000..1bdaf6e --- /dev/null +++ b/frontend/src/app/deposit/deposit.component.html @@ -0,0 +1,21 @@ +
+
+ {{ errorMsg }} +
+
+ + +
+ +
diff --git a/frontend/src/app/deposit/deposit.component.ts b/frontend/src/app/deposit/deposit.component.ts new file mode 100644 index 0000000..0d56c70 --- /dev/null +++ b/frontend/src/app/deposit/deposit.component.ts @@ -0,0 +1,51 @@ +import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { loadStripe, Stripe } from '@stripe/stripe-js'; +import { DepositService } from '../service/deposit.service'; +import { debounceTime } from 'rxjs'; +import { environment } from '../../environments/environment'; +import { NgIf } from '@angular/common'; + +@Component({ + selector: 'app-deposit', + standalone: true, + imports: [ReactiveFormsModule, NgIf], + templateUrl: './deposit.component.html', + styleUrl: './deposit.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class DepositComponent implements OnInit { + protected form!: FormGroup; + protected errorMsg = ''; + private stripe: Stripe | null = null; + private service: DepositService = inject(DepositService); + + async ngOnInit() { + this.form = new FormGroup({ + amount: new FormControl(50, [Validators.min(50)]), + }); + + this.form.controls['amount'].valueChanges.pipe(debounceTime(1000)).subscribe((value) => { + if (value < 50) { + this.errorMsg = 'Minimum Einzahlungsbetrag ist 50€'; + } + }); + + this.stripe = await loadStripe(environment.STRIPE_KEY); + } + + submit() { + if (!this.stripe) { + this.errorMsg = 'Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.'; + return; + } + if (!this.form.valid) { + this.errorMsg = 'Bitte geben Sie einen gültigen Betrag ein.'; + return; + } + + this.service.handleDeposit(this.form.value.amount as number).subscribe(({ sessionId }) => { + this.stripe?.redirectToCheckout({ sessionId }); + }); + } +} diff --git a/frontend/src/app/service/deposit.service.ts b/frontend/src/app/service/deposit.service.ts new file mode 100644 index 0000000..02c8ea1 --- /dev/null +++ b/frontend/src/app/service/deposit.service.ts @@ -0,0 +1,14 @@ +import { inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class DepositService { + private http: HttpClient = inject(HttpClient); + + handleDeposit(amount: number): Observable<{ sessionId: string }> { + return this.http.post<{ sessionId: string }>('/backend/deposit/checkout', { amount }); + } +} diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts new file mode 100644 index 0000000..53866cc --- /dev/null +++ b/frontend/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + STRIPE_KEY: + 'pk_test_51QrePYIvCfqz7ANgMizBorPpVjJ8S6gcaL4yvcMQnVaKyReqcQ6jqaQEF7aDZbDu8rNVsTZrw8ABek4ToxQX7KZe00jpGh8naG', +};