dev #8
@@ -212,15 +212,16 @@ public class OrderController {
|
|||||||
|
|
||||||
@GetMapping("/{orderId}/twint")
|
@GetMapping("/{orderId}/twint")
|
||||||
public ResponseEntity<Map<String, String>> getTwintPayment(@PathVariable UUID orderId) {
|
public ResponseEntity<Map<String, String>> getTwintPayment(@PathVariable UUID orderId) {
|
||||||
if (!orderRepo.existsById(orderId)) {
|
Order order = orderRepo.findById(orderId).orElse(null);
|
||||||
|
if (order == null) {
|
||||||
return ResponseEntity.notFound().build();
|
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);
|
String qrDataUri = "data:image/png;base64," + Base64.getEncoder().encodeToString(qrPng);
|
||||||
|
|
||||||
Map<String, String> data = new HashMap<>();
|
Map<String, String> data = new HashMap<>();
|
||||||
data.put("paymentUrl", twintPaymentService.getTwintPaymentUrl());
|
data.put("paymentUrl", twintPaymentService.getTwintPaymentUrl(order));
|
||||||
data.put("openUrl", "/api/orders/" + orderId + "/twint/open");
|
data.put("openUrl", "/api/orders/" + orderId + "/twint/open");
|
||||||
data.put("qrImageUrl", "/api/orders/" + orderId + "/twint/qr");
|
data.put("qrImageUrl", "/api/orders/" + orderId + "/twint/qr");
|
||||||
data.put("qrImageDataUri", qrDataUri);
|
data.put("qrImageDataUri", qrDataUri);
|
||||||
@@ -229,12 +230,13 @@ public class OrderController {
|
|||||||
|
|
||||||
@GetMapping("/{orderId}/twint/open")
|
@GetMapping("/{orderId}/twint/open")
|
||||||
public ResponseEntity<Void> openTwintPayment(@PathVariable UUID orderId) {
|
public ResponseEntity<Void> openTwintPayment(@PathVariable UUID orderId) {
|
||||||
if (!orderRepo.existsById(orderId)) {
|
Order order = orderRepo.findById(orderId).orElse(null);
|
||||||
|
if (order == null) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.status(302)
|
return ResponseEntity.status(302)
|
||||||
.location(URI.create(twintPaymentService.getTwintPaymentUrl()))
|
.location(URI.create(twintPaymentService.getTwintPaymentUrl(order)))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,12 +245,13 @@ public class OrderController {
|
|||||||
@PathVariable UUID orderId,
|
@PathVariable UUID orderId,
|
||||||
@RequestParam(defaultValue = "320") int size
|
@RequestParam(defaultValue = "320") int size
|
||||||
) {
|
) {
|
||||||
if (!orderRepo.existsById(orderId)) {
|
Order order = orderRepo.findById(orderId).orElse(null);
|
||||||
|
if (order == null) {
|
||||||
return ResponseEntity.notFound().build();
|
return ResponseEntity.notFound().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
int normalizedSize = Math.max(200, Math.min(size, 600));
|
int normalizedSize = Math.max(200, Math.min(size, 600));
|
||||||
byte[] png = twintPaymentService.generateQrPng(normalizedSize);
|
byte[] png = twintPaymentService.generateQrPng(order, normalizedSize);
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.IMAGE_PNG)
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ public class QuoteSessionController {
|
|||||||
session.setPricingVersion("v1");
|
session.setPricingVersion("v1");
|
||||||
// Default material/settings will be set when items are added or updated?
|
// Default material/settings will be set when items are added or updated?
|
||||||
// For now set safe defaults
|
// For now set safe defaults
|
||||||
session.setMaterialCode("pla_basic");
|
session.setMaterialCode("PLA");
|
||||||
session.setSupportsEnabled(false);
|
session.setSupportsEnabled(false);
|
||||||
session.setCreatedAt(OffsetDateTime.now());
|
session.setCreatedAt(OffsetDateTime.now());
|
||||||
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
|
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
|
||||||
@@ -125,6 +125,15 @@ public class QuoteSessionController {
|
|||||||
// Apply Basic/Advanced Logic
|
// Apply Basic/Advanced Logic
|
||||||
applyPrintSettings(settings);
|
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
|
// REAL SLICING
|
||||||
// 1. Pick Machine (default to first active or specific)
|
// 1. Pick Machine (default to first active or specific)
|
||||||
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
|
PrinterMachine machine = machineRepo.findFirstByIsActiveTrue()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class PrintSettingsDto {
|
|||||||
private String quality; // "draft", "standard", "high"
|
private String quality; // "draft", "standard", "high"
|
||||||
|
|
||||||
// Advanced Mode (Optional in Basic)
|
// Advanced Mode (Optional in Basic)
|
||||||
|
private Double nozzleDiameter;
|
||||||
private Double layerHeight;
|
private Double layerHeight;
|
||||||
private Double infillDensity;
|
private Double infillDensity;
|
||||||
private String infillPattern;
|
private String infillPattern;
|
||||||
|
|||||||
@@ -21,14 +21,37 @@ public class TwintPaymentService {
|
|||||||
this.twintPaymentUrl = twintPaymentUrl;
|
this.twintPaymentUrl = twintPaymentUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTwintPaymentUrl() {
|
public String getTwintPaymentUrl(com.printcalculator.entity.Order order) {
|
||||||
return twintPaymentUrl;
|
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 {
|
try {
|
||||||
|
String url = getTwintPaymentUrl(order);
|
||||||
// Use High Error Correction for financial QR codes
|
// 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
|
// Standard QR quiet zone is 4 modules
|
||||||
int borderModules = 4;
|
int borderModules = 4;
|
||||||
|
|||||||
@@ -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.host=${MAIL_HOST:mail.infomaniak.com}
|
||||||
spring.mail.port=${MAIL_PORT:587}
|
spring.mail.port=${MAIL_PORT:587}
|
||||||
spring.mail.username=${MAIL_USERNAME:info@3d-fab.ch}
|
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.auth=${MAIL_SMTP_AUTH:false}
|
||||||
spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:false}
|
spring.mail.properties.mail.smtp.starttls.enable=${MAIL_SMTP_STARTTLS:false}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,11 @@ export class CalculatorPageComponent implements OnInit {
|
|||||||
this.route.queryParams.subscribe(params => {
|
this.route.queryParams.subscribe(params => {
|
||||||
const sessionId = params['session'];
|
const sessionId = params['session'];
|
||||||
if (sessionId) {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ export class QuoteEstimatorService {
|
|||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
complexityMode: request.mode.toUpperCase(),
|
complexityMode: request.mode.toUpperCase(),
|
||||||
material: this.mapMaterial(request.material),
|
material: request.material,
|
||||||
quality: request.quality,
|
quality: request.quality,
|
||||||
supportsEnabled: request.supportEnabled,
|
supportsEnabled: request.supportEnabled,
|
||||||
color: item.color || '#FFFFFF',
|
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
|
// Consultation Data Transfer
|
||||||
private pendingConsultation = signal<{files: File[], message: string} | null>(null);
|
private pendingConsultation = signal<{files: File[], message: string} | null>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,17 @@
|
|||||||
<img *ngIf="twintQrUrl()" class="twint-qr" [src]="getTwintQrUrl()" (error)="onTwintQrError()" alt="TWINT payment QR" />
|
<img *ngIf="twintQrUrl()" class="twint-qr" [src]="getTwintQrUrl()" (error)="onTwintQrError()" alt="TWINT payment QR" />
|
||||||
<p>{{ 'PAYMENT.TWINT_DESC' | translate }}</p>
|
<p>{{ 'PAYMENT.TWINT_DESC' | translate }}</p>
|
||||||
<p class="billing-hint">{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}</p>
|
<p class="billing-hint">{{ 'PAYMENT.BILLING_INFO_HINT' | translate }}</p>
|
||||||
<div class="twint-mobile-action">
|
<div class="twint-mobile-action twint-button-container">
|
||||||
<app-button (click)="openTwintPayment()" [fullWidth]="true">
|
<button style="width: auto; height: 58px;
|
||||||
{{ 'PAYMENT.TWINT_OPEN' | translate }}
|
border-radius: 6px;
|
||||||
</app-button>
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
align-items: center;" (click)="openTwintPayment()">
|
||||||
|
<img style="width: auto; height: 58px" alt="Embedded TWINT button" [src]="getTwintButtonImageUrl()"/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="amount">{{ 'PAYMENT.TOTAL' | translate }}: {{ o.totalChf | currency:'CHF' }}</p>
|
<p class="amount">{{ 'PAYMENT.TOTAL' | translate }}: {{ o.totalChf | currency:'CHF' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -150,6 +150,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
margin-top: var(--space-3);
|
margin-top: var(--space-3);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.amount {
|
.amount {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
|
||||||
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
|
||||||
import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service';
|
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';
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -18,6 +18,7 @@ export class OrderComponent implements OnInit {
|
|||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private quoteService = inject(QuoteEstimatorService);
|
private quoteService = inject(QuoteEstimatorService);
|
||||||
|
private translate = inject(TranslateService);
|
||||||
|
|
||||||
orderId: string | null = null;
|
orderId: string | null = null;
|
||||||
selectedPaymentMethod: 'twint' | 'bill' | null = 'twint';
|
selectedPaymentMethod: 'twint' | 'bill' | null = 'twint';
|
||||||
@@ -101,6 +102,15 @@ export class OrderComponent implements OnInit {
|
|||||||
return this.twintQrUrl() ?? '';
|
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 {
|
onTwintQrError(): void {
|
||||||
this.twintQrUrl.set(null);
|
this.twintQrUrl.set(null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,7 @@
|
|||||||
"BANK_OWNER": "Titolare",
|
"BANK_OWNER": "Titolare",
|
||||||
"BANK_IBAN": "IBAN",
|
"BANK_IBAN": "IBAN",
|
||||||
"BANK_REF": "Riferimento",
|
"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)",
|
"DOWNLOAD_QR": "Scarica QR-Fattura (PDF)",
|
||||||
"CONFIRM": "Ho completato il pagamento",
|
"CONFIRM": "Ho completato il pagamento",
|
||||||
"SUMMARY_TITLE": "Riepilogo Ordine",
|
"SUMMARY_TITLE": "Riepilogo Ordine",
|
||||||
|
|||||||
Reference in New Issue
Block a user