mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-28 06:38:51 +00:00
trabajando en la tabla de transferencias
This commit is contained in:
@ -8,6 +8,7 @@ import org.springframework.context.MessageSource;
|
|||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
@ -17,7 +18,9 @@ import com.imprimelibros.erp.datatables.DataTable;
|
|||||||
import com.imprimelibros.erp.datatables.DataTablesParser;
|
import com.imprimelibros.erp.datatables.DataTablesParser;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
import com.imprimelibros.erp.datatables.DataTablesRequest;
|
||||||
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
import com.imprimelibros.erp.datatables.DataTablesResponse;
|
||||||
|
import com.imprimelibros.erp.i18n.TranslationService;
|
||||||
import com.imprimelibros.erp.payments.model.Payment;
|
import com.imprimelibros.erp.payments.model.Payment;
|
||||||
|
import com.imprimelibros.erp.payments.model.PaymentStatus;
|
||||||
import com.imprimelibros.erp.payments.model.PaymentTransaction;
|
import com.imprimelibros.erp.payments.model.PaymentTransaction;
|
||||||
import com.imprimelibros.erp.payments.model.PaymentTransactionStatus;
|
import com.imprimelibros.erp.payments.model.PaymentTransactionStatus;
|
||||||
import com.imprimelibros.erp.payments.model.PaymentTransactionType;
|
import com.imprimelibros.erp.payments.model.PaymentTransactionType;
|
||||||
@ -32,20 +35,34 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
@PreAuthorize("hasRole('SUPERADMIN')")
|
@PreAuthorize("hasRole('SUPERADMIN')")
|
||||||
public class PaymentController {
|
public class PaymentController {
|
||||||
|
|
||||||
private final MessageSource messageSource;
|
protected final MessageSource messageSource;
|
||||||
|
protected final TranslationService translationService;
|
||||||
protected final PaymentTransactionRepository repoPaymentTransaction;
|
protected final PaymentTransactionRepository repoPaymentTransaction;
|
||||||
protected final UserDao repoUser;
|
protected final UserDao repoUser;
|
||||||
|
|
||||||
public PaymentController(PaymentTransactionRepository repoPaymentTransaction, UserDao repoUser,
|
public PaymentController(PaymentTransactionRepository repoPaymentTransaction, UserDao repoUser,
|
||||||
MessageSource messageSource) {
|
MessageSource messageSource, TranslationService translationService) {
|
||||||
this.repoPaymentTransaction = repoPaymentTransaction;
|
this.repoPaymentTransaction = repoPaymentTransaction;
|
||||||
this.repoUser = repoUser;
|
this.repoUser = repoUser;
|
||||||
this.messageSource = messageSource;
|
this.messageSource = messageSource;
|
||||||
|
this.translationService = translationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping()
|
@GetMapping()
|
||||||
public String index() {
|
public String index(Model model, Locale locale) {
|
||||||
|
|
||||||
|
List<String> keys = List.of(
|
||||||
|
"app.cancelar",
|
||||||
|
"app.aceptar",
|
||||||
|
"pagos.refund.title",
|
||||||
|
"pagos.refund.text",
|
||||||
|
"pagos.refund.success",
|
||||||
|
"pagos.refund.error.general",
|
||||||
|
"pagos.refund.error.invalid-number");
|
||||||
|
|
||||||
|
Map<String, String> translations = translationService.getTranslations(locale, keys);
|
||||||
|
model.addAttribute("languageBundle", translations);
|
||||||
|
|
||||||
return "imprimelibros/pagos/gestion-pagos";
|
return "imprimelibros/pagos/gestion-pagos";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,6 +148,7 @@ public class PaymentController {
|
|||||||
return "<span class=\'badge bg-secondary btn-refund-payment \' data-dsOrderId=\'"
|
return "<span class=\'badge bg-secondary btn-refund-payment \' data-dsOrderId=\'"
|
||||||
+ p.getGatewayOrderId()
|
+ p.getGatewayOrderId()
|
||||||
+ "\' data-transactionId=\'" + pago.getPayment().getId()
|
+ "\' data-transactionId=\'" + pago.getPayment().getId()
|
||||||
|
+ "\' data-amount=\'" + (pago.getAmountCents() - p.getAmountRefundedCents())
|
||||||
+ "\' style=\'cursor: pointer;\'>"
|
+ "\' style=\'cursor: pointer;\'>"
|
||||||
+ messageSource.getMessage("pagos.table.devuelto", null, locale) + "</span>";
|
+ messageSource.getMessage("pagos.table.devuelto", null, locale) + "</span>";
|
||||||
}
|
}
|
||||||
@ -144,4 +162,123 @@ public class PaymentController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "datatable/transferencias", produces = "application/json")
|
||||||
|
@ResponseBody
|
||||||
|
public DataTablesResponse<Map<String, Object>> getDatatableTransferencias(HttpServletRequest request,
|
||||||
|
Locale locale) {
|
||||||
|
|
||||||
|
DataTablesRequest dt = DataTablesParser.from(request);
|
||||||
|
|
||||||
|
List<String> searchable = List.of(
|
||||||
|
// "client" no, porque lo calculas a posteriori
|
||||||
|
);
|
||||||
|
|
||||||
|
// Campos ordenables
|
||||||
|
List<String> orderable = List.of(
|
||||||
|
"transferId",
|
||||||
|
"status",
|
||||||
|
"amountCents",
|
||||||
|
"payment.amountRefundedCents",
|
||||||
|
"createdAt", "updatedAt");
|
||||||
|
|
||||||
|
Specification<PaymentTransaction> base = Specification.allOf(
|
||||||
|
(root, query, cb) -> cb.equal(root.get("status"), PaymentTransactionStatus.pending));
|
||||||
|
base = base.and((root, query, cb) -> cb.equal(root.get("type"), PaymentTransactionType.CAPTURE));
|
||||||
|
base = base.and((root, query, cb) -> cb.equal(root.get("payment").get("gateway"), "bank_transfer"));
|
||||||
|
|
||||||
|
String clientSearch = dt.getColumnSearch("client");
|
||||||
|
|
||||||
|
// 2) Si hay filtro, traducirlo a userIds y añadirlo al Specification
|
||||||
|
if (clientSearch != null) {
|
||||||
|
List<Long> userIds = repoUser.findIdsByFullNameLike(clientSearch.trim());
|
||||||
|
|
||||||
|
if (userIds.isEmpty()) {
|
||||||
|
// Ningún usuario coincide → forzamos 0 resultados
|
||||||
|
base = base.and((root, query, cb) -> cb.disjunction());
|
||||||
|
} else {
|
||||||
|
base = base.and((root, query, cb) -> root.join("payment").get("userId").in(userIds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Long total = repoPaymentTransaction.count(base);
|
||||||
|
|
||||||
|
return DataTable
|
||||||
|
.of(repoPaymentTransaction, PaymentTransaction.class, dt, searchable)
|
||||||
|
.orderable(orderable)
|
||||||
|
.add("created_at", pago -> Utils.formatDateTime(pago.getCreatedAt(), locale))
|
||||||
|
.add("processed_at", pago -> Utils.formatDateTime(pago.getProcessedAt(), locale))
|
||||||
|
.add("client", pago -> {
|
||||||
|
if (pago.getPayment() != null && pago.getPayment().getUserId() != null) {
|
||||||
|
Payment payment = pago.getPayment();
|
||||||
|
if (payment.getUserId() != null) {
|
||||||
|
Optional<User> user = repoUser.findById(payment.getUserId().longValue());
|
||||||
|
return user.map(User::getFullName).orElse("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
})
|
||||||
|
.add("transfer_id", pago -> {
|
||||||
|
if (pago.getPayment() != null) {
|
||||||
|
return "TRANSF-" + pago.getPayment().getGatewayOrderId();
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add("order_id", pago -> {
|
||||||
|
if (pago.getStatus() != PaymentTransactionStatus.pending) {
|
||||||
|
if (pago.getPayment() != null && pago.getPayment().getOrderId() != null) {
|
||||||
|
return pago.getPayment().getOrderId().toString();
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return messageSource.getMessage("pagos.transferencia.no-pedido", null, "Pendiente", locale);
|
||||||
|
|
||||||
|
}).add("amount_cents", pago -> Utils.formatCurrency(pago.getAmountCents() / 100.0, locale))
|
||||||
|
.add("amount_cents_refund", pago ->
|
||||||
|
|
||||||
|
{
|
||||||
|
Payment payment = pago.getPayment();
|
||||||
|
if (payment != null) {
|
||||||
|
return Utils.formatCurrency(payment.getAmountRefundedCents() / 100.0, locale);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}).add("status", pago -> {
|
||||||
|
switch (pago.getStatus()) {
|
||||||
|
case PaymentTransactionStatus.pending:
|
||||||
|
return messageSource.getMessage("pagos.table.estado.pending", null, "Pendiente", locale);
|
||||||
|
case PaymentTransactionStatus.succeeded:
|
||||||
|
return messageSource.getMessage("pagos.table.estado.succeeded", null, "Completada", locale);
|
||||||
|
case PaymentTransactionStatus.failed:
|
||||||
|
return messageSource.getMessage("pagos.table.estado.failed", null, "Fallido", locale);
|
||||||
|
default:
|
||||||
|
return pago.getStatus().name();
|
||||||
|
}
|
||||||
|
}).add("actions", pago -> {
|
||||||
|
Payment p = pago.getPayment();
|
||||||
|
if (p != null) {
|
||||||
|
String actions = "";
|
||||||
|
if (pago.getStatus() != PaymentTransactionStatus.succeeded) {
|
||||||
|
actions += "<span class=\'badge bg-secondary btn-mark-as-completed \' data-dsOrderId=\'"
|
||||||
|
+ p.getGatewayOrderId()
|
||||||
|
+ "\' data-transactionId=\'" + pago.getPayment().getId()
|
||||||
|
+ "\' style=\'cursor: pointer;\'>"
|
||||||
|
+ messageSource.getMessage("pagos.table.finalizar", null, locale) + "</span> ";
|
||||||
|
|
||||||
|
}
|
||||||
|
if (pago.getAmountCents() - p.getAmountRefundedCents() > 0) {
|
||||||
|
actions += "<span class=\'badge bg-secondary btn-refund-payment \' data-dsOrderId=\'"
|
||||||
|
+ p.getGatewayOrderId()
|
||||||
|
+ "\' data-transactionId=\'" + pago.getPayment().getId()
|
||||||
|
+ "\' data-amount=\'" + (pago.getAmountCents() - p.getAmountRefundedCents())
|
||||||
|
+ "\' style=\'cursor: pointer;\'>"
|
||||||
|
+ messageSource.getMessage("pagos.table.devuelto", null, locale) + "</span>";
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}).where(base).toJson(total);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -198,18 +198,7 @@ public class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (authorized) {
|
if (authorized) {
|
||||||
// GENERAR PEDIDO A PARTIR DEL CARRITO
|
processOrder(notif.cartId);
|
||||||
Cart cart = this.cartService.findById(notif.cartId);
|
|
||||||
if (cart != null) {
|
|
||||||
// Bloqueamos el carrito
|
|
||||||
this.cartService.lockCartById(cart.getId());
|
|
||||||
// order ID es generado dentro de createOrderFromCart donde se marcan los
|
|
||||||
// presupuestos como no editables
|
|
||||||
// Long orderId =
|
|
||||||
// this.cartService.pedidoService.createOrderFromCart(cart.getId(), p.getId());
|
|
||||||
// p.setOrderId(orderId);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payRepo.save(p);
|
payRepo.save(p);
|
||||||
@ -295,7 +284,6 @@ public class PaymentService {
|
|||||||
payRepo.save(p);
|
payRepo.save(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Payment createBankTransferPayment(Long cartId, long amountCents, String currency) {
|
public Payment createBankTransferPayment(Long cartId, long amountCents, String currency) {
|
||||||
Payment p = new Payment();
|
Payment p = new Payment();
|
||||||
@ -304,6 +292,10 @@ public class PaymentService {
|
|||||||
Cart cart = this.cartService.findById(cartId);
|
Cart cart = this.cartService.findById(cartId);
|
||||||
if (cart != null && cart.getUserId() != null) {
|
if (cart != null && cart.getUserId() != null) {
|
||||||
p.setUserId(cart.getUserId());
|
p.setUserId(cart.getUserId());
|
||||||
|
// En el orderId de la transferencia pendiente guardamos el ID del carrito
|
||||||
|
p.setOrderId(cartId);
|
||||||
|
// Se bloquea el carrito para evitar modificaciones mientras se procesa el pago
|
||||||
|
this.cartService.lockCartById(cartId);
|
||||||
}
|
}
|
||||||
|
|
||||||
p.setCurrency(currency);
|
p.setCurrency(currency);
|
||||||
@ -360,6 +352,11 @@ public class PaymentService {
|
|||||||
p.setCapturedAt(LocalDateTime.now());
|
p.setCapturedAt(LocalDateTime.now());
|
||||||
p.setStatus(PaymentStatus.captured);
|
p.setStatus(PaymentStatus.captured);
|
||||||
payRepo.save(p);
|
payRepo.save(p);
|
||||||
|
|
||||||
|
// 4) Procesar el pedido asociado al carrito (si existe)
|
||||||
|
if (p.getOrderId() != null) {
|
||||||
|
processOrder(p.getOrderId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRedsysAuthorized(RedsysService.RedsysNotification notif) {
|
private boolean isRedsysAuthorized(RedsysService.RedsysNotification notif) {
|
||||||
@ -376,4 +373,20 @@ public class PaymentService {
|
|||||||
return code >= 0 && code <= 99;
|
return code >= 0 && code <= 99;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean processOrder(Long cartId) {
|
||||||
|
// GENERAR PEDIDO A PARTIR DEL CARRITO
|
||||||
|
Cart cart = this.cartService.findById(cartId);
|
||||||
|
if (cart != null) {
|
||||||
|
// Bloqueamos el carrito
|
||||||
|
this.cartService.lockCartById(cart.getId());
|
||||||
|
// order ID es generado dentro de createOrderFromCart donde se marcan los
|
||||||
|
// presupuestos como no editables
|
||||||
|
// Long orderId =
|
||||||
|
// this.cartService.pedidoService.createOrderFromCart(cart.getId(), p.getId());
|
||||||
|
// p.setOrderId(orderId);
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,9 +163,9 @@ public class RedsysController {
|
|||||||
try {
|
try {
|
||||||
String idem = "refund-" + paymentId + "-" + amountCents + "-" + UUID.randomUUID();
|
String idem = "refund-" + paymentId + "-" + amountCents + "-" + UUID.randomUUID();
|
||||||
paymentService.refundViaRedsys(paymentId, amountCents, idem);
|
paymentService.refundViaRedsys(paymentId, amountCents, idem);
|
||||||
return ResponseEntity.ok("Refund solicitado");
|
return ResponseEntity.ok("{success:true}");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return ResponseEntity.badRequest().body("Error refund: " + e.getMessage());
|
return ResponseEntity.badRequest().body("{success:false, error: '" + e.getMessage() + "'}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -287,10 +287,6 @@ public class RedsysService {
|
|||||||
|
|
||||||
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
System.out.println("### Redsys refund REST request:\n" + json);
|
|
||||||
System.out.println("### HTTP " + response.statusCode());
|
|
||||||
System.out.println("### Redsys refund REST response:\n" + response.body());
|
|
||||||
|
|
||||||
if (response.statusCode() / 100 != 2)
|
if (response.statusCode() / 100 != 2)
|
||||||
throw new IllegalStateException("HTTP error Redsys refund: " + response.statusCode());
|
throw new IllegalStateException("HTTP error Redsys refund: " + response.statusCode());
|
||||||
|
|
||||||
@ -313,7 +309,6 @@ public class RedsysService {
|
|||||||
|
|
||||||
// Decodificar MerchantParameters de la respuesta
|
// Decodificar MerchantParameters de la respuesta
|
||||||
Map<String, Object> decoded = decodeMerchantParametersToMap(dsMerchantParametersResp);
|
Map<String, Object> decoded = decodeMerchantParametersToMap(dsMerchantParametersResp);
|
||||||
System.out.println("### Redsys refund decoded response:\n" + decoded);
|
|
||||||
|
|
||||||
String dsResponse = String.valueOf(decoded.get("Ds_Response"));
|
String dsResponse = String.valueOf(decoded.get("Ds_Response"));
|
||||||
if (!"0900".equals(dsResponse)) {
|
if (!"0900".equals(dsResponse)) {
|
||||||
|
|||||||
@ -1,403 +1,418 @@
|
|||||||
databaseChangeLog:
|
databaseChangeLog:
|
||||||
- changeSet:
|
- changeSet:
|
||||||
id: 0007-payments-core
|
id: 0007-payments-core
|
||||||
author: jjo
|
author: jjo
|
||||||
changes:
|
changes:
|
||||||
# 2) payments
|
# 2) payments
|
||||||
- createTable:
|
- createTable:
|
||||||
tableName: payments
|
tableName: payments
|
||||||
columns:
|
columns:
|
||||||
- column:
|
- column:
|
||||||
name: id
|
name: id
|
||||||
type: BIGINT AUTO_INCREMENT
|
type: BIGINT AUTO_INCREMENT
|
||||||
constraints:
|
constraints:
|
||||||
primaryKey: true
|
primaryKey: true
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: order_id
|
name: order_id
|
||||||
type: BIGINT
|
type: BIGINT
|
||||||
- column:
|
- column:
|
||||||
name: user_id
|
name: user_id
|
||||||
type: BIGINT
|
type: BIGINT
|
||||||
- column:
|
- column:
|
||||||
name: currency
|
name: currency
|
||||||
type: CHAR(3)
|
type: CHAR(3)
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: amount_total_cents
|
name: amount_total_cents
|
||||||
type: BIGINT
|
type: BIGINT
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: amount_captured_cents
|
name: amount_captured_cents
|
||||||
type: BIGINT
|
type: BIGINT
|
||||||
defaultValueNumeric: 0
|
defaultValueNumeric: 0
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: amount_refunded_cents
|
name: amount_refunded_cents
|
||||||
type: BIGINT
|
type: BIGINT
|
||||||
defaultValueNumeric: 0
|
defaultValueNumeric: 0
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: status
|
name: status
|
||||||
type: "ENUM('requires_payment_method','requires_action','authorized','captured','partially_refunded','refunded','canceled','failed')"
|
type: "ENUM('requires_payment_method','requires_action','authorized','captured','partially_refunded','refunded','canceled','failed')"
|
||||||
defaultValue: "requires_payment_method"
|
defaultValue: "requires_payment_method"
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: capture_method
|
name: capture_method
|
||||||
type: "ENUM('automatic','manual')"
|
type: "ENUM('automatic','manual')"
|
||||||
defaultValue: "automatic"
|
defaultValue: "automatic"
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: gateway
|
name: gateway
|
||||||
type: VARCHAR(32)
|
type: VARCHAR(32)
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: gateway_payment_id
|
name: gateway_payment_id
|
||||||
type: VARCHAR(128)
|
type: VARCHAR(128)
|
||||||
- column:
|
- column:
|
||||||
name: gateway_order_id
|
name: gateway_order_id
|
||||||
type: VARCHAR(12)
|
type: VARCHAR(12)
|
||||||
- column:
|
- column:
|
||||||
name: authorization_code
|
name: authorization_code
|
||||||
type: VARCHAR(32)
|
type: VARCHAR(32)
|
||||||
- column:
|
- column:
|
||||||
name: three_ds_status
|
name: three_ds_status
|
||||||
type: "ENUM('not_applicable','attempted','challenge','succeeded','failed')"
|
type: "ENUM('not_applicable','attempted','challenge','succeeded','failed')"
|
||||||
defaultValue: "not_applicable"
|
defaultValue: "not_applicable"
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: descriptor
|
name: descriptor
|
||||||
type: VARCHAR(22)
|
type: VARCHAR(22)
|
||||||
- column:
|
- column:
|
||||||
name: client_ip
|
name: client_ip
|
||||||
type: VARBINARY(16)
|
type: VARBINARY(16)
|
||||||
- column:
|
- column:
|
||||||
name: authorized_at
|
name: authorized_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
- column:
|
- column:
|
||||||
name: captured_at
|
name: captured_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
- column:
|
- column:
|
||||||
name: canceled_at
|
name: canceled_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
- column:
|
- column:
|
||||||
name: failed_at
|
name: failed_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
- column:
|
- column:
|
||||||
name: metadata
|
name: metadata
|
||||||
type: JSON
|
type: JSON
|
||||||
- column:
|
- column:
|
||||||
name: created_at
|
name: created_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
defaultValueComputed: CURRENT_TIMESTAMP
|
defaultValueComputed: CURRENT_TIMESTAMP
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
- column:
|
- column:
|
||||||
name: updated_at
|
name: updated_at
|
||||||
type: DATETIME
|
type: DATETIME
|
||||||
defaultValueComputed: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
defaultValueComputed: "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
|
||||||
constraints:
|
constraints:
|
||||||
nullable: false
|
nullable: false
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payments
|
||||||
|
indexName: idx_payments_order
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: order_id
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payments
|
||||||
|
indexName: idx_payments_gateway
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: gateway
|
||||||
|
- column:
|
||||||
|
name: gateway_payment_id
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payments
|
||||||
|
indexName: idx_payments_status
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: status
|
||||||
|
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: payments
|
||||||
|
columnNames: gateway, gateway_order_id
|
||||||
|
constraintName: uq_payments_gateway_order
|
||||||
|
|
||||||
|
# 3) payment_transactions
|
||||||
|
- createTable:
|
||||||
|
tableName: payment_transactions
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: id
|
||||||
|
type: BIGINT AUTO_INCREMENT
|
||||||
|
constraints:
|
||||||
|
primaryKey: true
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: payment_id
|
||||||
|
type: BIGINT
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: type
|
||||||
|
type: "ENUM('AUTH','CAPTURE','REFUND','VOID')"
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: status
|
||||||
|
type: "ENUM('pending','succeeded','failed')"
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: amount_cents
|
||||||
|
type: BIGINT
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: currency
|
||||||
|
type: CHAR(3)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: gateway_transaction_id
|
||||||
|
type: VARCHAR(128)
|
||||||
|
- column:
|
||||||
|
name: gateway_response_code
|
||||||
|
type: VARCHAR(64)
|
||||||
|
- column:
|
||||||
|
name: avs_result
|
||||||
|
type: VARCHAR(8)
|
||||||
|
- column:
|
||||||
|
name: cvv_result
|
||||||
|
type: VARCHAR(8)
|
||||||
|
- column:
|
||||||
|
name: three_ds_version
|
||||||
|
type: VARCHAR(16)
|
||||||
|
- column:
|
||||||
|
name: idempotency_key
|
||||||
|
type: VARCHAR(128)
|
||||||
|
- column:
|
||||||
|
name: request_payload
|
||||||
|
type: JSON
|
||||||
|
- column:
|
||||||
|
name: response_payload
|
||||||
|
type: JSON
|
||||||
|
- column:
|
||||||
|
name: processed_at
|
||||||
|
type: DATETIME
|
||||||
|
- column:
|
||||||
|
name: created_at
|
||||||
|
type: DATETIME
|
||||||
|
defaultValueComputed: CURRENT_TIMESTAMP
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- addForeignKeyConstraint:
|
||||||
|
baseTableName: payment_transactions
|
||||||
|
baseColumnNames: payment_id
|
||||||
|
referencedTableName: payments
|
||||||
|
referencedColumnNames: id
|
||||||
|
constraintName: fk_tx_payment
|
||||||
|
onDelete: CASCADE
|
||||||
|
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: payment_transactions
|
||||||
|
columnNames: gateway_transaction_id
|
||||||
|
constraintName: uq_tx_gateway_txid
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payment_transactions
|
||||||
|
indexName: idx_tx_pay
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: payment_id
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payment_transactions
|
||||||
|
indexName: idx_tx_type_status
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: type
|
||||||
|
- column:
|
||||||
|
name: status
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: payment_transactions
|
||||||
|
indexName: idx_tx_idem
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: idempotency_key
|
||||||
|
|
||||||
|
# 4) refunds
|
||||||
|
- createTable:
|
||||||
|
tableName: refunds
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: id
|
||||||
|
type: BIGINT AUTO_INCREMENT
|
||||||
|
constraints:
|
||||||
|
primaryKey: true
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: payment_id
|
||||||
|
type: BIGINT
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: transaction_id
|
||||||
|
type: BIGINT
|
||||||
|
- column:
|
||||||
|
name: amount_cents
|
||||||
|
type: BIGINT
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: reason
|
||||||
|
type: "ENUM('customer_request','partial_return','pricing_adjustment','duplicate','fraud','other')"
|
||||||
|
defaultValue: "customer_request"
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: status
|
||||||
|
type: "ENUM('pending','succeeded','failed','canceled')"
|
||||||
|
defaultValue: "pending"
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: requested_by_user_id
|
||||||
|
type: BIGINT
|
||||||
|
- column:
|
||||||
|
name: requested_at
|
||||||
|
type: DATETIME
|
||||||
|
defaultValueComputed: CURRENT_TIMESTAMP
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: processed_at
|
||||||
|
type: DATETIME
|
||||||
|
- column:
|
||||||
|
name: gateway_refund_id
|
||||||
|
type: VARCHAR(128)
|
||||||
|
- column:
|
||||||
|
name: notes
|
||||||
|
type: VARCHAR(500)
|
||||||
|
- column:
|
||||||
|
name: metadata
|
||||||
|
type: JSON
|
||||||
|
|
||||||
|
- addForeignKeyConstraint:
|
||||||
|
baseTableName: refunds
|
||||||
|
baseColumnNames: payment_id
|
||||||
|
referencedTableName: payments
|
||||||
|
referencedColumnNames: id
|
||||||
|
constraintName: fk_ref_payment
|
||||||
|
onDelete: CASCADE
|
||||||
|
|
||||||
|
- addForeignKeyConstraint:
|
||||||
|
baseTableName: refunds
|
||||||
|
baseColumnNames: transaction_id
|
||||||
|
referencedTableName: payment_transactions
|
||||||
|
referencedColumnNames: id
|
||||||
|
constraintName: fk_ref_tx
|
||||||
|
onDelete: SET NULL
|
||||||
|
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: refunds
|
||||||
|
columnNames: gateway_refund_id
|
||||||
|
constraintName: uq_refund_gateway_id
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: refunds
|
||||||
|
indexName: idx_ref_pay
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: payment_id
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: refunds
|
||||||
|
indexName: idx_ref_status
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: status
|
||||||
|
|
||||||
|
# 5) webhook_events
|
||||||
|
- createTable:
|
||||||
|
tableName: webhook_events
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: id
|
||||||
|
type: BIGINT AUTO_INCREMENT
|
||||||
|
constraints:
|
||||||
|
primaryKey: true
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: provider
|
||||||
|
type: VARCHAR(32)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: event_type
|
||||||
|
type: VARCHAR(64)
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: event_id
|
||||||
|
type: VARCHAR(128)
|
||||||
|
- column:
|
||||||
|
name: signature
|
||||||
|
type: VARCHAR(512)
|
||||||
|
- column:
|
||||||
|
name: payload
|
||||||
|
type: JSON
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: processed
|
||||||
|
type: TINYINT(1)
|
||||||
|
defaultValueNumeric: 0
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: processed_at
|
||||||
|
type: DATETIME
|
||||||
|
- column:
|
||||||
|
name: attempts
|
||||||
|
type: INT
|
||||||
|
defaultValueNumeric: 0
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
- column:
|
||||||
|
name: last_error
|
||||||
|
type: VARCHAR(500)
|
||||||
|
- column:
|
||||||
|
name: created_at
|
||||||
|
type: DATETIME
|
||||||
|
defaultValueComputed: CURRENT_TIMESTAMP
|
||||||
|
constraints:
|
||||||
|
nullable: false
|
||||||
|
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: webhook_events
|
||||||
|
columnNames: provider, event_id
|
||||||
|
constraintName: uq_webhook_provider_event
|
||||||
|
|
||||||
|
- createIndex:
|
||||||
|
tableName: webhook_events
|
||||||
|
indexName: idx_webhook_processed
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: processed
|
||||||
|
|
||||||
|
|
||||||
- createIndex:
|
rollback:
|
||||||
tableName: payments
|
# Se borran las tablas en orden inverso de dependencias
|
||||||
indexName: idx_payments_order
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: order_id
|
|
||||||
|
|
||||||
- createIndex:
|
- dropTable:
|
||||||
tableName: payments
|
tableName: webhook_events
|
||||||
indexName: idx_payments_gateway
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: gateway
|
|
||||||
- column:
|
|
||||||
name: gateway_payment_id
|
|
||||||
|
|
||||||
- createIndex:
|
- dropTable:
|
||||||
tableName: payments
|
tableName: refunds
|
||||||
indexName: idx_payments_status
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: status
|
|
||||||
|
|
||||||
- addUniqueConstraint:
|
- dropTable:
|
||||||
tableName: payments
|
tableName: payment_transactions
|
||||||
columnNames: gateway, gateway_order_id
|
|
||||||
constraintName: uq_payments_gateway_order
|
|
||||||
|
|
||||||
# 3) payment_transactions
|
- dropTable:
|
||||||
- createTable:
|
tableName: payments
|
||||||
tableName: payment_transactions
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: id
|
|
||||||
type: BIGINT AUTO_INCREMENT
|
|
||||||
constraints:
|
|
||||||
primaryKey: true
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: payment_id
|
|
||||||
type: BIGINT
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: type
|
|
||||||
type: "ENUM('AUTH','CAPTURE','REFUND','VOID')"
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: status
|
|
||||||
type: "ENUM('pending','succeeded','failed')"
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: amount_cents
|
|
||||||
type: BIGINT
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: currency
|
|
||||||
type: CHAR(3)
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: gateway_transaction_id
|
|
||||||
type: VARCHAR(128)
|
|
||||||
- column:
|
|
||||||
name: gateway_response_code
|
|
||||||
type: VARCHAR(64)
|
|
||||||
- column:
|
|
||||||
name: avs_result
|
|
||||||
type: VARCHAR(8)
|
|
||||||
- column:
|
|
||||||
name: cvv_result
|
|
||||||
type: VARCHAR(8)
|
|
||||||
- column:
|
|
||||||
name: three_ds_version
|
|
||||||
type: VARCHAR(16)
|
|
||||||
- column:
|
|
||||||
name: idempotency_key
|
|
||||||
type: VARCHAR(128)
|
|
||||||
- column:
|
|
||||||
name: request_payload
|
|
||||||
type: JSON
|
|
||||||
- column:
|
|
||||||
name: response_payload
|
|
||||||
type: JSON
|
|
||||||
- column:
|
|
||||||
name: processed_at
|
|
||||||
type: DATETIME
|
|
||||||
- column:
|
|
||||||
name: created_at
|
|
||||||
type: DATETIME
|
|
||||||
defaultValueComputed: CURRENT_TIMESTAMP
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
|
|
||||||
- addForeignKeyConstraint:
|
|
||||||
baseTableName: payment_transactions
|
|
||||||
baseColumnNames: payment_id
|
|
||||||
referencedTableName: payments
|
|
||||||
referencedColumnNames: id
|
|
||||||
constraintName: fk_tx_payment
|
|
||||||
onDelete: CASCADE
|
|
||||||
|
|
||||||
- addUniqueConstraint:
|
|
||||||
tableName: payment_transactions
|
|
||||||
columnNames: gateway_transaction_id
|
|
||||||
constraintName: uq_tx_gateway_txid
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: payment_transactions
|
|
||||||
indexName: idx_tx_pay
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: payment_id
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: payment_transactions
|
|
||||||
indexName: idx_tx_type_status
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: type
|
|
||||||
- column:
|
|
||||||
name: status
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: payment_transactions
|
|
||||||
indexName: idx_tx_idem
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: idempotency_key
|
|
||||||
|
|
||||||
# 4) refunds
|
|
||||||
- createTable:
|
|
||||||
tableName: refunds
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: id
|
|
||||||
type: BIGINT AUTO_INCREMENT
|
|
||||||
constraints:
|
|
||||||
primaryKey: true
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: payment_id
|
|
||||||
type: BIGINT
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: transaction_id
|
|
||||||
type: BIGINT
|
|
||||||
- column:
|
|
||||||
name: amount_cents
|
|
||||||
type: BIGINT
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: reason
|
|
||||||
type: "ENUM('customer_request','partial_return','pricing_adjustment','duplicate','fraud','other')"
|
|
||||||
defaultValue: "customer_request"
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: status
|
|
||||||
type: "ENUM('pending','succeeded','failed','canceled')"
|
|
||||||
defaultValue: "pending"
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: requested_by_user_id
|
|
||||||
type: BIGINT
|
|
||||||
- column:
|
|
||||||
name: requested_at
|
|
||||||
type: DATETIME
|
|
||||||
defaultValueComputed: CURRENT_TIMESTAMP
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: processed_at
|
|
||||||
type: DATETIME
|
|
||||||
- column:
|
|
||||||
name: gateway_refund_id
|
|
||||||
type: VARCHAR(128)
|
|
||||||
- column:
|
|
||||||
name: notes
|
|
||||||
type: VARCHAR(500)
|
|
||||||
- column:
|
|
||||||
name: metadata
|
|
||||||
type: JSON
|
|
||||||
|
|
||||||
- addForeignKeyConstraint:
|
|
||||||
baseTableName: refunds
|
|
||||||
baseColumnNames: payment_id
|
|
||||||
referencedTableName: payments
|
|
||||||
referencedColumnNames: id
|
|
||||||
constraintName: fk_ref_payment
|
|
||||||
onDelete: CASCADE
|
|
||||||
|
|
||||||
- addForeignKeyConstraint:
|
|
||||||
baseTableName: refunds
|
|
||||||
baseColumnNames: transaction_id
|
|
||||||
referencedTableName: payment_transactions
|
|
||||||
referencedColumnNames: id
|
|
||||||
constraintName: fk_ref_tx
|
|
||||||
onDelete: SET NULL
|
|
||||||
|
|
||||||
- addUniqueConstraint:
|
|
||||||
tableName: refunds
|
|
||||||
columnNames: gateway_refund_id
|
|
||||||
constraintName: uq_refund_gateway_id
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: refunds
|
|
||||||
indexName: idx_ref_pay
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: payment_id
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: refunds
|
|
||||||
indexName: idx_ref_status
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: status
|
|
||||||
|
|
||||||
# 5) webhook_events
|
|
||||||
- createTable:
|
|
||||||
tableName: webhook_events
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: id
|
|
||||||
type: BIGINT AUTO_INCREMENT
|
|
||||||
constraints:
|
|
||||||
primaryKey: true
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: provider
|
|
||||||
type: VARCHAR(32)
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: event_type
|
|
||||||
type: VARCHAR(64)
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: event_id
|
|
||||||
type: VARCHAR(128)
|
|
||||||
- column:
|
|
||||||
name: signature
|
|
||||||
type: VARCHAR(512)
|
|
||||||
- column:
|
|
||||||
name: payload
|
|
||||||
type: JSON
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: processed
|
|
||||||
type: TINYINT(1)
|
|
||||||
defaultValueNumeric: 0
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: processed_at
|
|
||||||
type: DATETIME
|
|
||||||
- column:
|
|
||||||
name: attempts
|
|
||||||
type: INT
|
|
||||||
defaultValueNumeric: 0
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
- column:
|
|
||||||
name: last_error
|
|
||||||
type: VARCHAR(500)
|
|
||||||
- column:
|
|
||||||
name: created_at
|
|
||||||
type: DATETIME
|
|
||||||
defaultValueComputed: CURRENT_TIMESTAMP
|
|
||||||
constraints:
|
|
||||||
nullable: false
|
|
||||||
|
|
||||||
- addUniqueConstraint:
|
|
||||||
tableName: webhook_events
|
|
||||||
columnNames: provider, event_id
|
|
||||||
constraintName: uq_webhook_provider_event
|
|
||||||
|
|
||||||
- createIndex:
|
|
||||||
tableName: webhook_events
|
|
||||||
indexName: idx_webhook_processed
|
|
||||||
columns:
|
|
||||||
- column:
|
|
||||||
name: processed
|
|
||||||
|
|
||||||
|
|||||||
@ -26,3 +26,22 @@ databaseChangeLog:
|
|||||||
sql: |
|
sql: |
|
||||||
CREATE UNIQUE INDEX uq_carts_user_active
|
CREATE UNIQUE INDEX uq_carts_user_active
|
||||||
ON carts (user_id, active_flag);
|
ON carts (user_id, active_flag);
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
# 🔙 1) Eliminar el índice nuevo basado en active_flag
|
||||||
|
- sql:
|
||||||
|
sql: |
|
||||||
|
ALTER TABLE carts
|
||||||
|
DROP INDEX uq_carts_user_active;
|
||||||
|
|
||||||
|
# 🔙 2) Eliminar la columna generada active_flag
|
||||||
|
- sql:
|
||||||
|
sql: |
|
||||||
|
ALTER TABLE carts
|
||||||
|
DROP COLUMN active_flag;
|
||||||
|
|
||||||
|
# 🔙 3) Restaurar el índice único original (user_id, status)
|
||||||
|
- sql:
|
||||||
|
sql: |
|
||||||
|
CREATE UNIQUE INDEX uq_carts_user_active
|
||||||
|
ON carts (user_id, status);
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
databaseChangeLog:
|
|
||||||
- changeSet:
|
|
||||||
id: 0009-add-composite-unique-txid-type
|
|
||||||
author: JJO
|
|
||||||
changes:
|
|
||||||
# 1️⃣ Eliminar el índice único anterior
|
|
||||||
- dropUniqueConstraint:
|
|
||||||
constraintName: uq_tx_gateway_txid
|
|
||||||
tableName: payment_transactions
|
|
||||||
|
|
||||||
# 2️⃣ Crear índice único compuesto por gateway_transaction_id + type
|
|
||||||
- addUniqueConstraint:
|
|
||||||
tableName: payment_transactions
|
|
||||||
columnNames: gateway_transaction_id, type
|
|
||||||
constraintName: uq_tx_gateway_txid_type
|
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
databaseChangeLog:
|
||||||
|
- changeSet:
|
||||||
|
id: 0009-drop-unique-refund-gateway-id
|
||||||
|
author: JJO
|
||||||
|
changes:
|
||||||
|
# 1️⃣ Eliminar la UNIQUE constraint sobre gateway_refund_id
|
||||||
|
- dropUniqueConstraint:
|
||||||
|
constraintName: uq_refund_gateway_id
|
||||||
|
tableName: refunds
|
||||||
|
|
||||||
|
# 2️⃣ Crear un índice normal (no único) para acelerar búsquedas por gateway_refund_id
|
||||||
|
- createIndex:
|
||||||
|
tableName: refunds
|
||||||
|
indexName: idx_refunds_gateway_refund_id
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: gateway_refund_id
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
# 🔙 1) Eliminar el índice normal creado en este changeSet
|
||||||
|
- dropIndex:
|
||||||
|
indexName: idx_refunds_gateway_refund_id
|
||||||
|
tableName: refunds
|
||||||
|
|
||||||
|
# 🔙 2) Restaurar la UNIQUE constraint original
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: refunds
|
||||||
|
columnNames: gateway_refund_id
|
||||||
|
constraintName: uq_refund_gateway_id
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
databaseChangeLog:
|
||||||
|
- changeSet:
|
||||||
|
id: 0010-drop-unique-tx-gateway
|
||||||
|
author: JJO
|
||||||
|
changes:
|
||||||
|
# 1️⃣ Eliminar la UNIQUE constraint sobre (gateway_transaction_id, type)
|
||||||
|
- dropUniqueConstraint:
|
||||||
|
constraintName: uq_tx_gateway_txid_type
|
||||||
|
tableName: payment_transactions
|
||||||
|
|
||||||
|
# 2️⃣ Crear un índice normal (no único) sobre gateway_transaction_id
|
||||||
|
# para poder seguir buscando rápido por este campo
|
||||||
|
- createIndex:
|
||||||
|
tableName: payment_transactions
|
||||||
|
indexName: idx_payment_tx_gateway_txid
|
||||||
|
columns:
|
||||||
|
- column:
|
||||||
|
name: gateway_transaction_id
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
# 🔙 1) Eliminar el índice normal creado en este changeSet
|
||||||
|
- dropIndex:
|
||||||
|
indexName: idx_payment_tx_gateway_txid
|
||||||
|
tableName: payment_transactions
|
||||||
|
|
||||||
|
# 🔙 2) Restaurar la UNIQUE constraint original
|
||||||
|
- addUniqueConstraint:
|
||||||
|
tableName: payment_transactions
|
||||||
|
columnNames: gateway_transaction_id, type
|
||||||
|
constraintName: uq_tx_gateway_txid_type
|
||||||
@ -17,3 +17,5 @@ databaseChangeLog:
|
|||||||
file: db/changelog/changesets/0008-update-cart-status-constraint.yml
|
file: db/changelog/changesets/0008-update-cart-status-constraint.yml
|
||||||
- include:
|
- include:
|
||||||
file: db/changelog/changesets/0009-add-composite-unique-txid-type.yml
|
file: db/changelog/changesets/0009-add-composite-unique-txid-type.yml
|
||||||
|
- include:
|
||||||
|
file: db/changelog/changesets/0010-drop-unique-tx-gateway.yml
|
||||||
@ -11,3 +11,21 @@ pagos.table.devuelto=Devolución
|
|||||||
pagos.table.fecha=Fecha
|
pagos.table.fecha=Fecha
|
||||||
pagos.table.estado=Estado
|
pagos.table.estado=Estado
|
||||||
pagos.table.acciones=Acciones
|
pagos.table.acciones=Acciones
|
||||||
|
|
||||||
|
pagos.table.concepto-transferencia=Concepto
|
||||||
|
pagos.table.estado-transferencia=Estado
|
||||||
|
pagos.table.fecha-created=Fecha creación
|
||||||
|
pagos.table.fecha-procesed=Fecha procesada
|
||||||
|
|
||||||
|
pagos.table.estado.pending=Pendiente
|
||||||
|
pagos.table.estado.succeeded=Completada
|
||||||
|
pagos.table.estado.failed=Fallido
|
||||||
|
pagos.table.finalizar=Finalizar
|
||||||
|
|
||||||
|
pagos.transferencia.no-pedido=No disponible
|
||||||
|
|
||||||
|
pagos.refund.title=Devolución de Pago Redsys
|
||||||
|
pagos.refund.text=Introduce la cantidad a devolver (en euros):
|
||||||
|
pagos.refund.success=Devolución solicitada con éxito. Si no se refleja inmediatamente, espere unos minutos y actualiza la página.
|
||||||
|
pagos.refund.error.general=Error al procesar la devolución
|
||||||
|
pagos.refund.error.invalid-number=Cantidad inválida para la devolución
|
||||||
|
|||||||
@ -76,11 +76,20 @@ $(() => {
|
|||||||
$(document).on('click', '.btn-refund-payment', function () {
|
$(document).on('click', '.btn-refund-payment', function () {
|
||||||
const dsOrderId = $(this).data('dsorderid');
|
const dsOrderId = $(this).data('dsorderid');
|
||||||
const transactionId = $(this).data('transactionid');
|
const transactionId = $(this).data('transactionid');
|
||||||
|
const maxAmountCents = $(this).data('amount');
|
||||||
// show swal confirmation with input for amount to refund
|
// show swal confirmation with input for amount to refund
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: '¿Estás seguro de que deseas devolver este pago?',
|
showCancelButton: true,
|
||||||
text: 'Introduce la cantidad a devolver (en euros):',
|
buttonsStyling: false,
|
||||||
|
title: window.languageBundle['pagos.refund.title'],
|
||||||
|
text: window.languageBundle['pagos.refund.text'],
|
||||||
input: 'number',
|
input: 'number',
|
||||||
|
confirmButtonText: window.languageBundle['app.aceptar'] || 'Seleccionar',
|
||||||
|
cancelButtonText: window.languageBundle['app.cancelar'] || 'Cancelar',
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary me-2',
|
||||||
|
cancelButton: 'btn btn-light',
|
||||||
|
},
|
||||||
inputAttributes: {
|
inputAttributes: {
|
||||||
min: 0,
|
min: 0,
|
||||||
}
|
}
|
||||||
@ -88,7 +97,15 @@ $(() => {
|
|||||||
if (result.isConfirmed) {
|
if (result.isConfirmed) {
|
||||||
const amountToRefund = parseFloat(result.value);
|
const amountToRefund = parseFloat(result.value);
|
||||||
if (isNaN(amountToRefund) || amountToRefund <= 0) {
|
if (isNaN(amountToRefund) || amountToRefund <= 0) {
|
||||||
Swal.fire('Error', 'Cantidad inválida para la devolución.', 'error');
|
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;
|
||||||
|
}
|
||||||
|
if (amountToRefund*100 > maxAmountCents) {
|
||||||
|
showSwal('Error', window.languageBundle['pagos.refund.error.invalid-number'], 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -98,14 +115,91 @@ $(() => {
|
|||||||
amountCents: amountToRefund*100
|
amountCents: amountToRefund*100
|
||||||
}
|
}
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
|
response = typeof response === 'string' ? JSON.parse(response) : response;
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
Swal.fire('Éxito', 'Pago devuelto con éxito.', 'success');
|
showSwal('Éxito', window.languageBundle['pagos.refund.success'], 'success');
|
||||||
table.draw();
|
$('#pagos-redsys-datatable').DataTable().draw();
|
||||||
} else {
|
} else {
|
||||||
Swal.fire('Error', 'No se pudo procesar la devolución.', 'error');
|
showSwal('Error', window.languageBundle['pagos.refund.error.general'], 'error');
|
||||||
|
$('#pagos-redsys-datatable').DataTable().draw();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}).fail(() => {
|
||||||
|
showSwal('Error', window.languageBundle['pagos.refund.error.general'], 'error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const tableT = new DataTable('#pagos-transferencias-datatable', {
|
||||||
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
|
orderCellsTop: true,
|
||||||
|
pageLength: 50,
|
||||||
|
lengthMenu: [10, 25, 50, 100, 500],
|
||||||
|
language: { url: '/assets/libs/datatables/i18n/' + language + '.json' },
|
||||||
|
responsive: true,
|
||||||
|
dom: 'lBrtip',
|
||||||
|
buttons: {
|
||||||
|
dom: {
|
||||||
|
button: {
|
||||||
|
className: 'btn btn-sm btn-outline-primary me-1'
|
||||||
|
},
|
||||||
|
buttons: [
|
||||||
|
{ extend: 'copy' },
|
||||||
|
{ extend: 'csv' },
|
||||||
|
{ extend: 'excel' },
|
||||||
|
{ extend: 'pdf' },
|
||||||
|
{ extend: 'print' },
|
||||||
|
{ extend: 'colvis' }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ajax: {
|
||||||
|
url: '/pagos/datatable/transferencias',
|
||||||
|
method: 'GET',
|
||||||
|
},
|
||||||
|
order: [[7, 'desc']], // Ordena por fecha por defecto
|
||||||
|
columns: [
|
||||||
|
{ data: 'client', name: 'client', orderable: true },
|
||||||
|
{ data: 'transfer_id', name: 'transfer_id', orderable: true },
|
||||||
|
{ data: 'status', name: 'status', orderable: true },
|
||||||
|
{ data: 'order_id', name: 'payment.orderId', orderable: true },
|
||||||
|
{ data: 'amount_cents', name: 'amountCents', orderable: true },
|
||||||
|
{ data: 'amount_cents_refund', name: 'amountCentsRefund', orderable: true },
|
||||||
|
{ data: 'created_at', name: 'createdAt', orderable: true },
|
||||||
|
{ data: 'processed_at', name: 'processedAt', orderable: true },
|
||||||
|
{ data: 'actions', name: 'actions', orderable: false, searchable: false }
|
||||||
|
|
||||||
|
],
|
||||||
|
columnDefs: [{ targets: -1, orderable: false, searchable: false }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fila de filtros = segunda fila del thead (index 1)
|
||||||
|
$('#pagos-redsys-datatable thead tr:eq(1) th').each(function (colIdx) {
|
||||||
|
const input = $(this).find('input');
|
||||||
|
if (input.length === 0) return; // columnas sin filtro
|
||||||
|
|
||||||
|
input.on('keyup change', function () {
|
||||||
|
const value = this.value;
|
||||||
|
if (table.column(colIdx).search() !== value) {
|
||||||
|
table.column(colIdx).search(value).draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function showSwal(title, text, icon) {
|
||||||
|
Swal.fire({
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
icon: icon,
|
||||||
|
buttonsStyling: false,
|
||||||
|
confirmButtonText: window.languageBundle['app.aceptar'] || 'Aceptar',
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'btn btn-secondary',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -67,11 +67,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="arrow-transferencias" role="tabpanel">
|
<div class="tab-pane" id="arrow-transferencias" role="tabpanel">
|
||||||
<div></div>
|
<div></div>
|
||||||
<!---
|
|
||||||
<div
|
<div
|
||||||
th:insert="~{imprimelibros/presupuestos/presupuesto-list-items/tabla-anonimos :: tabla-anonimos}">
|
th:insert="~{imprimelibros/pagos/tabla-transferencias :: tabla-transferencias}">
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
<div th:fragment="tabla-transferencias">
|
||||||
|
<table id="pagos-transferencias-datatable" class="table table-striped table-nowrap responsive w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" th:text="#{pagos.table.cliente.nombre}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.concepto-transferencia}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.estado-transferencia}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.pedido.id}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.cantidad}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.devuelto}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.fecha-created}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.fecha-procesed}"></th>
|
||||||
|
<th scope="col" th:text="#{pagos.table.acciones}">Acciones</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><input type="text" class="form-control form-control-sm redsys-filter" /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm redsys-filter" /></th>
|
||||||
|
<th><input type="text" class="form-control form-control-sm redsys-filter" /></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
<th></th> <!-- Acciones (sin filtro) -->
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user