From 57f6301e038eac0d307d586e48e8ea4488201da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joe=20K=C3=BCng?= Date: Tue, 24 Feb 2026 10:07:04 +0100 Subject: [PATCH] feat(back-end front-end): integration with twint --- .../controller/OrderController.java | 17 +++++----- .../controller/QuoteSessionController.java | 11 ++++++- .../printcalculator/dto/PrintSettingsDto.java | 1 + .../service/TwintPaymentService.java | 31 ++++++++++++++++--- .../src/main/resources/application.properties | 2 +- .../calculator/calculator-page.component.ts | 6 +++- .../services/quote-estimator.service.ts | 10 +----- .../app/features/order/order.component.html | 15 ++++++--- .../app/features/order/order.component.scss | 2 ++ .../src/app/features/order/order.component.ts | 12 ++++++- frontend/src/assets/i18n/it.json | 2 +- 11 files changed, 80 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/com/printcalculator/controller/OrderController.java b/backend/src/main/java/com/printcalculator/controller/OrderController.java index bae5d52..0d46ab9 100644 --- a/backend/src/main/java/com/printcalculator/controller/OrderController.java +++ b/backend/src/main/java/com/printcalculator/controller/OrderController.java @@ -212,15 +212,16 @@ public class OrderController { @GetMapping("/{orderId}/twint") public ResponseEntity> getTwintPayment(@PathVariable UUID orderId) { - if (!orderRepo.existsById(orderId)) { + Order order = orderRepo.findById(orderId).orElse(null); + if (order == null) { return ResponseEntity.notFound().build(); } - byte[] qrPng = twintPaymentService.generateQrPng(360); + byte[] qrPng = twintPaymentService.generateQrPng(order, 360); String qrDataUri = "data:image/png;base64," + Base64.getEncoder().encodeToString(qrPng); Map data = new HashMap<>(); - data.put("paymentUrl", twintPaymentService.getTwintPaymentUrl()); + data.put("paymentUrl", twintPaymentService.getTwintPaymentUrl(order)); data.put("openUrl", "/api/orders/" + orderId + "/twint/open"); data.put("qrImageUrl", "/api/orders/" + orderId + "/twint/qr"); data.put("qrImageDataUri", qrDataUri); @@ -229,12 +230,13 @@ public class OrderController { @GetMapping("/{orderId}/twint/open") public ResponseEntity openTwintPayment(@PathVariable UUID orderId) { - if (!orderRepo.existsById(orderId)) { + Order order = orderRepo.findById(orderId).orElse(null); + if (order == null) { return ResponseEntity.notFound().build(); } return ResponseEntity.status(302) - .location(URI.create(twintPaymentService.getTwintPaymentUrl())) + .location(URI.create(twintPaymentService.getTwintPaymentUrl(order))) .build(); } @@ -243,12 +245,13 @@ public class OrderController { @PathVariable UUID orderId, @RequestParam(defaultValue = "320") int size ) { - if (!orderRepo.existsById(orderId)) { + Order order = orderRepo.findById(orderId).orElse(null); + if (order == null) { return ResponseEntity.notFound().build(); } int normalizedSize = Math.max(200, Math.min(size, 600)); - byte[] png = twintPaymentService.generateQrPng(normalizedSize); + byte[] png = twintPaymentService.generateQrPng(order, normalizedSize); return ResponseEntity.ok() .contentType(MediaType.IMAGE_PNG) diff --git a/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java b/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java index b58c224..5202e65 100644 --- a/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java +++ b/backend/src/main/java/com/printcalculator/controller/QuoteSessionController.java @@ -71,7 +71,7 @@ public class QuoteSessionController { session.setPricingVersion("v1"); // Default material/settings will be set when items are added or updated? // For now set safe defaults - session.setMaterialCode("pla_basic"); + session.setMaterialCode("PLA"); session.setSupportsEnabled(false); session.setCreatedAt(OffsetDateTime.now()); session.setExpiresAt(OffsetDateTime.now().plusDays(30)); @@ -125,6 +125,15 @@ public class QuoteSessionController { // Apply Basic/Advanced Logic applyPrintSettings(settings); + // Update session global settings from the most recent item added + session.setMaterialCode(settings.getMaterial()); + session.setNozzleDiameterMm(BigDecimal.valueOf(settings.getNozzleDiameter() != null ? settings.getNozzleDiameter() : 0.4)); + session.setLayerHeightMm(BigDecimal.valueOf(settings.getLayerHeight() != null ? settings.getLayerHeight() : 0.2)); + session.setInfillPattern(settings.getInfillPattern()); + session.setInfillPercent(settings.getInfillDensity() != null ? settings.getInfillDensity().intValue() : 20); + session.setSupportsEnabled(settings.getSupportsEnabled() != null ? settings.getSupportsEnabled() : false); + sessionRepo.save(session); + // REAL SLICING // 1. Pick Machine (default to first active or specific) PrinterMachine machine = machineRepo.findFirstByIsActiveTrue() diff --git a/backend/src/main/java/com/printcalculator/dto/PrintSettingsDto.java b/backend/src/main/java/com/printcalculator/dto/PrintSettingsDto.java index c63565d..75aafc9 100644 --- a/backend/src/main/java/com/printcalculator/dto/PrintSettingsDto.java +++ b/backend/src/main/java/com/printcalculator/dto/PrintSettingsDto.java @@ -15,6 +15,7 @@ public class PrintSettingsDto { private String quality; // "draft", "standard", "high" // Advanced Mode (Optional in Basic) + private Double nozzleDiameter; private Double layerHeight; private Double infillDensity; private String infillPattern; diff --git a/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java b/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java index 97982eb..539d339 100644 --- a/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java +++ b/backend/src/main/java/com/printcalculator/service/TwintPaymentService.java @@ -21,14 +21,37 @@ public class TwintPaymentService { this.twintPaymentUrl = twintPaymentUrl; } - public String getTwintPaymentUrl() { - return twintPaymentUrl; + public String getTwintPaymentUrl(com.printcalculator.entity.Order order) { + StringBuilder urlBuilder = new StringBuilder(twintPaymentUrl); + + if (order != null) { + if (order.getTotalChf() != null) { + urlBuilder.append("&amount=").append(order.getTotalChf().toPlainString()); + } + + String orderNumber = order.getOrderNumber(); + if (orderNumber == null && order.getId() != null) { + orderNumber = order.getId().toString(); + } + + if (orderNumber != null) { + try { + urlBuilder.append("&trxInfo=").append(order.getId()); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } + + return urlBuilder.toString(); } - public byte[] generateQrPng(int sizePx) { + public byte[] generateQrPng(com.printcalculator.entity.Order order, int sizePx) { try { + String url = getTwintPaymentUrl(order); // Use High Error Correction for financial QR codes - QrCode qrCode = QrCode.encodeText(twintPaymentUrl, QrCode.Ecc.HIGH); + QrCode qrCode = QrCode.encodeText(url, QrCode.Ecc.HIGH); // Standard QR quiet zone is 4 modules int borderModules = 4; diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 424defb..d74ad77 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -31,7 +31,7 @@ payment.twint.url=${TWINT_PAYMENT_URL:https://go.twint.ch/1/e/tw?tw=acq.gERQQytO spring.mail.host=${MAIL_HOST:mail.infomaniak.com} spring.mail.port=${MAIL_PORT:587} spring.mail.username=${MAIL_USERNAME:info@3d-fab.ch} -spring.mail.password=${MAIL_PASSWORD:ht*44k+Tq39R+R-O} +spring.mail.password=${MAIL_PASSWORD:} spring.mail.properties.mail.smtp.auth=${MAIL_SMTP_AUTH:false} spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:false} diff --git a/frontend/src/app/features/calculator/calculator-page.component.ts b/frontend/src/app/features/calculator/calculator-page.component.ts index 4a3d4c0..c0e362f 100644 --- a/frontend/src/app/features/calculator/calculator-page.component.ts +++ b/frontend/src/app/features/calculator/calculator-page.component.ts @@ -49,7 +49,11 @@ export class CalculatorPageComponent implements OnInit { this.route.queryParams.subscribe(params => { const sessionId = params['session']; if (sessionId) { - this.loadSession(sessionId); + // Avoid reloading if we just calculated this session + const currentRes = this.result(); + if (!currentRes || currentRes.sessionId !== sessionId) { + this.loadSession(sessionId); + } } }); } diff --git a/frontend/src/app/features/calculator/services/quote-estimator.service.ts b/frontend/src/app/features/calculator/services/quote-estimator.service.ts index 22ae525..6f9f89e 100644 --- a/frontend/src/app/features/calculator/services/quote-estimator.service.ts +++ b/frontend/src/app/features/calculator/services/quote-estimator.service.ts @@ -215,7 +215,7 @@ export class QuoteEstimatorService { const settings = { complexityMode: request.mode.toUpperCase(), - material: this.mapMaterial(request.material), + material: request.material, quality: request.quality, supportsEnabled: request.supportEnabled, color: item.color || '#FFFFFF', @@ -317,14 +317,6 @@ export class QuoteEstimatorService { }); } - private mapMaterial(mat: string): string { - const m = mat.toUpperCase(); - if (m.includes('PLA')) return 'pla_basic'; - if (m.includes('PETG')) return 'petg_basic'; - if (m.includes('TPU')) return 'tpu_95a'; - return 'pla_basic'; - } - // Consultation Data Transfer private pendingConsultation = signal<{files: File[], message: string} | null>(null); diff --git a/frontend/src/app/features/order/order.component.html b/frontend/src/app/features/order/order.component.html index eae4443..cb51108 100644 --- a/frontend/src/app/features/order/order.component.html +++ b/frontend/src/app/features/order/order.component.html @@ -71,10 +71,17 @@ TWINT payment QR

{{ 'PAYMENT.TWINT_DESC' | translate }}

{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}

-
- - {{ 'PAYMENT.TWINT_OPEN' | translate }} - +
+

{{ 'PAYMENT.TOTAL' | translate }}: {{ o.totalChf | currency:'CHF' }}

diff --git a/frontend/src/app/features/order/order.component.scss b/frontend/src/app/features/order/order.component.scss index a678b56..c3db3ba 100644 --- a/frontend/src/app/features/order/order.component.scss +++ b/frontend/src/app/features/order/order.component.scss @@ -150,6 +150,8 @@ width: 100%; max-width: 320px; margin-top: var(--space-3); + display: flex; + justify-content: center; } .amount { diff --git a/frontend/src/app/features/order/order.component.ts b/frontend/src/app/features/order/order.component.ts index 3510d31..3b032ea 100644 --- a/frontend/src/app/features/order/order.component.ts +++ b/frontend/src/app/features/order/order.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { AppButtonComponent } from '../../shared/components/app-button/app-button.component'; import { AppCardComponent } from '../../shared/components/app-card/app-card.component'; import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service'; -import { TranslateModule } from '@ngx-translate/core'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { environment } from '../../../environments/environment'; @Component({ @@ -18,6 +18,7 @@ export class OrderComponent implements OnInit { private route = inject(ActivatedRoute); private router = inject(Router); private quoteService = inject(QuoteEstimatorService); + private translate = inject(TranslateService); orderId: string | null = null; selectedPaymentMethod: 'twint' | 'bill' | null = 'twint'; @@ -101,6 +102,15 @@ export class OrderComponent implements OnInit { return this.twintQrUrl() ?? ''; } + getTwintButtonImageUrl(): string { + const lang = this.translate.currentLang; + if (lang === 'de') { + return 'https://go.twint.ch/static/img/button_dark_de.svg'; + } + // Default to EN for everything else (it, fr, en) as instructed or if not DE + return 'https://go.twint.ch/static/img/button_dark_en.svg'; + } + onTwintQrError(): void { this.twintQrUrl.set(null); } diff --git a/frontend/src/assets/i18n/it.json b/frontend/src/assets/i18n/it.json index 0ae91bf..1d8e7c3 100644 --- a/frontend/src/assets/i18n/it.json +++ b/frontend/src/assets/i18n/it.json @@ -262,7 +262,7 @@ "BANK_OWNER": "Titolare", "BANK_IBAN": "IBAN", "BANK_REF": "Riferimento", - "BILLING_INFO_HINT": "Aggiungi le informazioni uguali a quelle della fatturazione.", + "BILLING_INFO_HINT": "Abbiamo compilato i campi per te, per favore non modificare il motivo del pagamento", "DOWNLOAD_QR": "Scarica QR-Fattura (PDF)", "CONFIRM": "Ho completato il pagamento", "SUMMARY_TITLE": "Riepilogo Ordine",