From f13eeb940cd28d288055a2b7e5311386e6d09773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Jim=C3=A9nez?= Date: Thu, 6 Nov 2025 13:49:15 +0100 Subject: [PATCH] terminado pagos --- .../erp/payments/PaymentController.java | 21 +- .../erp/payments/PaymentService.java | 80 +- src/main/resources/i18n/pedidos_es.properties | 4 +- .../pages/imprimelibros/checkout/checkout.js | 14 +- .../js/pages/imprimelibros/pagos/pagos.js | 65 +- .../assets/js/pages/passowrd-create.init.js | 94 ++ .../assets/js/pages/password-addon.init.js | 21 + .../imprimelibros/cart/_cartItem.html | 2 +- .../imprimelibros/checkout/_summary.html | 10 +- .../partials/app-chat-userprofile-canvas.html | 298 ++++ .../templates/theme/partials/customizer.html | 833 +++++++++++ .../templates/theme/partials/footer.html | 22 + .../templates/theme/partials/head-css.html | 16 + .../templates/theme/partials/page-title.html | 25 + .../templates/theme/partials/sidebar.html | 1290 +++++++++++++++++ .../templates/theme/partials/title-meta.html | 15 + .../templates/theme/partials/topbar.html | 740 ++++++++++ .../theme/partials/vendor-scripts.html | 14 + 18 files changed, 3552 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/static/assets/js/pages/passowrd-create.init.js create mode 100644 src/main/resources/static/assets/js/pages/password-addon.init.js create mode 100644 src/main/resources/templates/theme/partials/app-chat-userprofile-canvas.html create mode 100644 src/main/resources/templates/theme/partials/customizer.html create mode 100644 src/main/resources/templates/theme/partials/footer.html create mode 100644 src/main/resources/templates/theme/partials/head-css.html create mode 100644 src/main/resources/templates/theme/partials/page-title.html create mode 100644 src/main/resources/templates/theme/partials/sidebar.html create mode 100644 src/main/resources/templates/theme/partials/title-meta.html create mode 100644 src/main/resources/templates/theme/partials/topbar.html create mode 100644 src/main/resources/templates/theme/partials/vendor-scripts.html diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java index bd441a2..f47421f 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentController.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentController.java @@ -11,6 +11,7 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.ResponseBody; @@ -276,7 +277,8 @@ public class PaymentController { + messageSource.getMessage("pagos.table.finalizar", null, locale) + " "; } - if ((pago.getAmountCents() - p.getAmountRefundedCents() > 0) && pago.getStatus() == PaymentTransactionStatus.succeeded) { + if ((pago.getAmountCents() - p.getAmountRefundedCents() > 0) + && pago.getStatus() == PaymentTransactionStatus.succeeded) { actions += "> refundTransfer(@PathVariable Long id, + @RequestParam("amountCents") Long amountCents) { + + Map response; + try { + paymentService.refundBankTransfer(id, amountCents); + response = Map.of("success", true); + return ResponseEntity.ok(response); + + } catch (Exception e) { + e.printStackTrace(); + response = Map.of("success", false); + response.put("error", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } } diff --git a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java index 23bb3e2..804c6f8 100644 --- a/src/main/java/com/imprimelibros/erp/payments/PaymentService.java +++ b/src/main/java/com/imprimelibros/erp/payments/PaymentService.java @@ -312,7 +312,7 @@ public class PaymentService { tx.setCurrency(currency); // tx.setProcessedAt(null); // la dejas nula hasta que se confirme txRepo.save(tx); - + return p; } @@ -358,6 +358,84 @@ public class PaymentService { } } + /** + * Devuelve (total o parcialmente) un pago hecho por transferencia bancaria. + * - Solo permite gateway = "bank_transfer". + * - Crea un Refund + PaymentTransaction de tipo REFUND. + * - Actualiza amountRefundedCents y el estado del Payment. + */ + @Transactional + public Refund refundBankTransfer(Long paymentId, long amountCents) { + Payment p = payRepo.findById(paymentId) + .orElseThrow(() -> new IllegalArgumentException("Payment no encontrado: " + paymentId)); + + if (!"bank_transfer".equals(p.getGateway())) { + throw new IllegalStateException("El Payment " + paymentId + " no es de tipo bank_transfer"); + } + + if (amountCents <= 0) { + throw new IllegalArgumentException("El importe de devolución debe ser > 0"); + } + + // Solo tiene sentido devolver si está capturado o ya parcialmente devuelto + if (p.getStatus() != PaymentStatus.captured + && p.getStatus() != PaymentStatus.partially_refunded) { + throw new IllegalStateException( + "El Payment " + paymentId + " no está capturado; estado actual: " + p.getStatus()); + } + + long maxRefundable = p.getAmountCapturedCents() - p.getAmountRefundedCents(); + if (amountCents > maxRefundable) { + throw new IllegalStateException( + "Importe de devolución supera lo todavía reembolsable. " + + "maxRefundable=" + maxRefundable + " requested=" + amountCents); + } + + LocalDateTime now = LocalDateTime.now(); + + // 1) Crear Refund (para transferencias lo marcamos como SUCCEEDED directamente) + Refund refund = new Refund(); + refund.setPayment(p); + refund.setAmountCents(amountCents); + // reason usa el valor por defecto (customer_request); si quieres otro, cámbialo + // aquí + refund.setStatus(RefundStatus.succeeded); + refund.setRequestedAt(now); + refund.setProcessedAt(now); + // requestedByUserId, notes, metadata -> opcionales, déjalos en null si no los + // usas + refund = refundRepo.save(refund); + + // 2) Crear transacción de tipo REFUND + PaymentTransaction tx = new PaymentTransaction(); + tx.setPayment(p); + tx.setType(PaymentTransactionType.REFUND); + tx.setStatus(PaymentTransactionStatus.succeeded); + tx.setAmountCents(amountCents); + tx.setCurrency(p.getCurrency()); + tx.setProcessedAt(now); + // gatewayTransactionId lo dejamos null → el índice UNIQUE permite múltiples + // NULL + tx = txRepo.save(tx); + + // Vincular el Refund con la transacción + refund.setTransaction(tx); + refundRepo.save(refund); + + // 3) Actualizar Payment: total devuelto y estado + p.setAmountRefundedCents(p.getAmountRefundedCents() + amountCents); + + if (p.getAmountRefundedCents().equals(p.getAmountCapturedCents())) { + p.setStatus(PaymentStatus.refunded); + } else { + p.setStatus(PaymentStatus.partially_refunded); + } + + payRepo.save(p); + + return refund; + } + private boolean isRedsysAuthorized(RedsysService.RedsysNotification notif) { if (notif.response == null) { return false; diff --git a/src/main/resources/i18n/pedidos_es.properties b/src/main/resources/i18n/pedidos_es.properties index 8ec7add..50b8b27 100644 --- a/src/main/resources/i18n/pedidos_es.properties +++ b/src/main/resources/i18n/pedidos_es.properties @@ -3,7 +3,6 @@ checkout.summary=Resumen de la compra checkout.billing-address=Dirección de facturación checkout.payment=Método de pago - checkout.billing-address.title=Seleccione una dirección checkout.billing-address.new-address=Nueva dirección checkout.billing-address.select-placeholder=Buscar en direcciones... @@ -15,4 +14,5 @@ checkout.payment.bank-transfer=Transferencia bancaria checkout.error.payment=Error al procesar el pago: el pago ha sido cancelado o rechazado Por favor, inténtelo de nuevo. checkout.success.payment=Pago realizado con éxito. Gracias por su compra. -checkout.make-payment=Realizar el pago \ No newline at end of file +checkout.make-payment=Realizar el pago +checkout.authorization-required=Certifico que tengo los derechos para imprimir los archivos incluidos en mi pedido y me hago responsable en caso de reclamación de los mismos \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js index b3f1673..cd97df1 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/checkout/checkout.js @@ -18,6 +18,16 @@ $(() => { $('#addBillingAddressBtn').on('click', seleccionarDireccionEnvio); + $('#authorization-required').on('change', function () { + if($(this).is(':checked')) { + if ($('#direccion-div .direccion-card').length > 0) { + $('#btn-checkout').prop('disabled', false); + } + } else { + $('#btn-checkout').prop('disabled', true); + } + }); + async function seleccionarDireccionEnvio() { @@ -133,7 +143,9 @@ $(() => { const html = await response.text(); $('#direccion-div').append(html); $('#addBillingAddressBtn').addClass('d-none'); - $('#btn-checkout').prop('disabled', false); + if ($('#authorization-required').is(':checked')) { + $('#btn-checkout').prop('disabled', false); + } hideLoader(); return true; } diff --git a/src/main/resources/static/assets/js/pages/imprimelibros/pagos/pagos.js b/src/main/resources/static/assets/js/pages/imprimelibros/pagos/pagos.js index dade1f3..0c8c348 100644 --- a/src/main/resources/static/assets/js/pages/imprimelibros/pagos/pagos.js +++ b/src/main/resources/static/assets/js/pages/imprimelibros/pagos/pagos.js @@ -83,7 +83,7 @@ $(() => { buttonsStyling: false, title: window.languageBundle['pagos.refund.title'], text: window.languageBundle['pagos.refund.text'], - input: 'number', + input: 'text', confirmButtonText: window.languageBundle['app.aceptar'] || 'Seleccionar', cancelButtonText: window.languageBundle['app.cancelar'] || 'Cancelar', customClass: { @@ -95,16 +95,17 @@ $(() => { } }).then((result) => { if (result.isConfirmed) { - const amountToRefund = parseFloat(result.value); + const normalized = result.value.replace(',', '.'); + const amountToRefund = parseFloat(normalized); if (isNaN(amountToRefund) || amountToRefund <= 0) { showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error'); return; } - if (amountToRefund*100 > maxAmountCents) { + if (amountToRefund * 100 > maxAmountCents) { showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error'); return; } - if (amountToRefund*100 > maxAmountCents) { + if (amountToRefund * 100 > maxAmountCents) { showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error'); return; } @@ -112,7 +113,7 @@ $(() => { url: '/pagos/redsys/refund/' + transactionId, method: 'POST', data: { - amountCents: amountToRefund*100 + amountCents: amountToRefund * 100 } }).then((response) => { response = typeof response === 'string' ? JSON.parse(response) : response; @@ -223,6 +224,60 @@ $(() => { }); }); + $(document).on('click', '.btn-transfer-refund', function () { + const transferId = $(this).data('transactionid'); + const maxAmountCents = $(this).data('amount'); + // show swal confirmation with input for amount to refund + Swal.fire({ + showCancelButton: true, + buttonsStyling: false, + title: window.languageBundle['pagos.refund.title'], + text: window.languageBundle['pagos.refund.text'], + input: 'text', + confirmButtonText: window.languageBundle['app.aceptar'] || 'Seleccionar', + cancelButtonText: window.languageBundle['app.cancelar'] || 'Cancelar', + customClass: { + confirmButton: 'btn btn-secondary me-2', + cancelButton: 'btn btn-light', + }, + inputAttributes: { + min: 0, + } + }).then((result) => { + if (result.isConfirmed) { + const normalized = result.value.replace(',', '.'); + const amountToRefund = parseFloat(normalized); + if (isNaN(amountToRefund) || amountToRefund <= 0) { + showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error'); + return; + } + if (amountToRefund * 100 > maxAmountCents) { + showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error'); + return; + } + $.ajax({ + url: '/pagos/transfer/refund/' + transferId, + method: 'POST', + data: { + amountCents: amountToRefund * 100 + } + }).then((response) => { + response = typeof response === 'string' ? JSON.parse(response) : response; + if (response.success) { + showSwal('Éxito', window.languageBundle['pagos.refund.success'], 'success'); + $('#pagos-transferencias-datatable').DataTable().draw(); + } else { + showSwal('Error', window.languageBundle['pagos.refund.error.general'], 'error'); + $('#pagos-transferencias-datatable').DataTable().draw(); + + } + }).fail(() => { + showSwal('Error', window.languageBundle['pagos.refund.error.general'], 'error'); + }); + } + }); + }); + function showSwal(title, text, icon) { Swal.fire({ title: title, diff --git a/src/main/resources/static/assets/js/pages/passowrd-create.init.js b/src/main/resources/static/assets/js/pages/passowrd-create.init.js new file mode 100644 index 0000000..0e3733f --- /dev/null +++ b/src/main/resources/static/assets/js/pages/passowrd-create.init.js @@ -0,0 +1,94 @@ +/* +Template Name: Velzon - Admin & Dashboard Template +Author: Themesbrand +Website: https://Themesbrand.com/ +Contact: Themesbrand@gmail.com +File: Password addon Js File +*/ + +// password addon +Array.from(document.querySelectorAll("form .auth-pass-inputgroup")).forEach(function (item) { + Array.from(item.querySelectorAll(".password-addon")).forEach(function (subitem) { + subitem.addEventListener("click", function (event) { + var passwordInput = item.querySelector(".password-input"); + if (passwordInput.type === "password") { + passwordInput.type = "text"; + } else { + passwordInput.type = "password"; + } + }); + }); + }); + +// passowrd match +var password = document.getElementById("password-input"), + confirm_password = document.getElementById("confirm-password-input"); + +function validatePassword() { + if (password.value != confirm_password.value) { + confirm_password.setCustomValidity("Passwords Don't Match"); + } else { + confirm_password.setCustomValidity(""); + } +} + +//Password validation +password.onchange = validatePassword; + +var myInput = document.getElementById("password-input"); +var letter = document.getElementById("pass-lower"); +var capital = document.getElementById("pass-upper"); +var number = document.getElementById("pass-number"); +var length = document.getElementById("pass-length"); + +// When the user clicks on the password field, show the message box +myInput.onfocus = function () { + document.getElementById("password-contain").style.display = "block"; +}; + +// When the user clicks outside of the password field, hide the password-contain box +myInput.onblur = function () { + document.getElementById("password-contain").style.display = "none"; +}; + +// When the user starts to type something inside the password field +myInput.onkeyup = function () { + // Validate lowercase letters + var lowerCaseLetters = /[a-z]/g; + if (myInput.value.match(lowerCaseLetters)) { + letter.classList.remove("invalid"); + letter.classList.add("valid"); + } else { + letter.classList.remove("valid"); + letter.classList.add("invalid"); + } + + // Validate capital letters + var upperCaseLetters = /[A-Z]/g; + if (myInput.value.match(upperCaseLetters)) { + capital.classList.remove("invalid"); + capital.classList.add("valid"); + } else { + capital.classList.remove("valid"); + capital.classList.add("invalid"); + } + + // Validate numbers + var numbers = /[0-9]/g; + if (myInput.value.match(numbers)) { + number.classList.remove("invalid"); + number.classList.add("valid"); + } else { + number.classList.remove("valid"); + number.classList.add("invalid"); + } + + // Validate length + if (myInput.value.length >= 8) { + length.classList.remove("invalid"); + length.classList.add("valid"); + } else { + length.classList.remove("valid"); + length.classList.add("invalid"); + } +}; \ No newline at end of file diff --git a/src/main/resources/static/assets/js/pages/password-addon.init.js b/src/main/resources/static/assets/js/pages/password-addon.init.js new file mode 100644 index 0000000..74fb510 --- /dev/null +++ b/src/main/resources/static/assets/js/pages/password-addon.init.js @@ -0,0 +1,21 @@ +/* +Template Name: Velzon - Admin & Dashboard Template +Author: Themesbrand +Website: https://Themesbrand.com/ +Contact: Themesbrand@gmail.com +File: Password addon Js File +*/ + +// password addon +Array.from(document.querySelectorAll("form .auth-pass-inputgroup")).forEach(function (item) { + Array.from(item.querySelectorAll(".password-addon")).forEach(function (subitem) { + subitem.addEventListener("click", function (event) { + var passwordInput = item.querySelector(".password-input"); + if (passwordInput.type === "password") { + passwordInput.type = "text"; + } else { + passwordInput.type = "password"; + } + }); + }); +}); \ No newline at end of file diff --git a/src/main/resources/templates/imprimelibros/cart/_cartItem.html b/src/main/resources/templates/imprimelibros/cart/_cartItem.html index 01de3dc..3afa59d 100644 --- a/src/main/resources/templates/imprimelibros/cart/_cartItem.html +++ b/src/main/resources/templates/imprimelibros/cart/_cartItem.html @@ -123,7 +123,7 @@
-
Envio +
Envio del pedido
diff --git a/src/main/resources/templates/imprimelibros/checkout/_summary.html b/src/main/resources/templates/imprimelibros/checkout/_summary.html index 288dc03..83a1309 100644 --- a/src/main/resources/templates/imprimelibros/checkout/_summary.html +++ b/src/main/resources/templates/imprimelibros/checkout/_summary.html @@ -38,9 +38,17 @@ +
+ + + +
- + diff --git a/src/main/resources/templates/theme/partials/app-chat-userprofile-canvas.html b/src/main/resources/templates/theme/partials/app-chat-userprofile-canvas.html new file mode 100644 index 0000000..d4fe052 --- /dev/null +++ b/src/main/resources/templates/theme/partials/app-chat-userprofile-canvas.html @@ -0,0 +1,298 @@ + + + +
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+ + + +
+ +
+
+
+ +
+
+ +
+
Lisa + Parker +
+

Online

+
+ +
+ + + + + + + +
+
+ +
+
Personal Details
+
+

Number

+
+(256) 2451 8974
+
+
+

Email

+
lisaparker@gmail.com
+
+
+

Location

+
California, USA
+
+
+ +
+
Attached Files
+ +
+
+
+
+
+
+ +
+
+
+
+
App + pages.zip
+
2.2MB
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
Velzon + admin.ppt
+
2.4MB
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
Images.zip
+
1.2MB
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
bg-pattern.png
+
1.1MB
+
+
+
+ + +
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/customizer.html b/src/main/resources/templates/theme/partials/customizer.html new file mode 100644 index 0000000..c03043f --- /dev/null +++ b/src/main/resources/templates/theme/partials/customizer.html @@ -0,0 +1,833 @@ + + + +
+ + + + + +
+
+
+ Loading... +
+
+
+ +
+
+ +
+
+ + +
+
+
Theme Customizer
+ + +
+
+
+
+
Layout
+

Choose your layout

+ +
+
+
+ + +
+
Vertical
+
+
+
+ + +
+
Horizontal
+
+
+
+ + +
+
Two Column
+
+ + +
+
+ + +
+
Semi Box
+
+ +
+ +
Color Scheme
+

Choose Light or Dark Scheme.

+ +
+
+
+
+ + +
+
Light
+
+ +
+
+ + +
+
Dark
+
+
+
+ + + +
+
Layout Width
+

Choose Fluid or Boxed layout.

+ +
+
+
+ + +
+
Fluid
+
+
+
+ + +
+
Boxed
+
+
+
+ +
+
Layout Position
+

Choose Fixed or Scrollable Layout Position.

+ +
+ + + + + +
+
+
Topbar Color
+

Choose Light or Dark Topbar Color.

+ +
+
+
+ + +
+
Light
+
+
+
+ + +
+
Dark
+
+
+ + + + + + + + +
+
Preloader
+

Choose a preloader.

+ +
+
+ +
Enable
+
+
+ +
Disable
+
+
+ +
+ + +
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/footer.html b/src/main/resources/templates/theme/partials/footer.html new file mode 100644 index 0000000..2df59aa --- /dev/null +++ b/src/main/resources/templates/theme/partials/footer.html @@ -0,0 +1,22 @@ + + + +
+
+
+
+
+ © Velzon. +
+
+
+ Design & Develop by Themesbrand +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/head-css.html b/src/main/resources/templates/theme/partials/head-css.html new file mode 100644 index 0000000..3f3f01b --- /dev/null +++ b/src/main/resources/templates/theme/partials/head-css.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/page-title.html b/src/main/resources/templates/theme/partials/page-title.html new file mode 100644 index 0000000..85a85c8 --- /dev/null +++ b/src/main/resources/templates/theme/partials/page-title.html @@ -0,0 +1,25 @@ + + + +
+ +
+
+
+

+ +
+ +
+ +
+
+
+ +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/sidebar.html b/src/main/resources/templates/theme/partials/sidebar.html new file mode 100644 index 0000000..bc4d592 --- /dev/null +++ b/src/main/resources/templates/theme/partials/sidebar.html @@ -0,0 +1,1290 @@ + + + +
+ + + + +
+
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/title-meta.html b/src/main/resources/templates/theme/partials/title-meta.html new file mode 100644 index 0000000..b4c3ee3 --- /dev/null +++ b/src/main/resources/templates/theme/partials/title-meta.html @@ -0,0 +1,15 @@ + + + +
+ + + + + + + +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/topbar.html b/src/main/resources/templates/theme/partials/topbar.html new file mode 100644 index 0000000..343383c --- /dev/null +++ b/src/main/resources/templates/theme/partials/topbar.html @@ -0,0 +1,740 @@ + + + +
+
+
+ +
+
+ + + +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/theme/partials/vendor-scripts.html b/src/main/resources/templates/theme/partials/vendor-scripts.html new file mode 100644 index 0000000..76818a7 --- /dev/null +++ b/src/main/resources/templates/theme/partials/vendor-scripts.html @@ -0,0 +1,14 @@ + + + +
+ + + + + + +
+ + + \ No newline at end of file