mirror of
https://git.imnavajas.es/jjimenez/erp-imprimelibros.git
synced 2026-01-13 00:48:49 +00:00
añadidos ficheros a falta de modificar el servicio y el controlador redsys
This commit is contained in:
183
src/main/java/com/imprimelibros/erp/payments/PaymentService.java
Normal file
183
src/main/java/com/imprimelibros/erp/payments/PaymentService.java
Normal file
@ -0,0 +1,183 @@
|
||||
package com.imprimelibros.erp.payments;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imprimelibros.erp.payments.model.*;
|
||||
import com.imprimelibros.erp.payments.repo.PaymentRepository;
|
||||
import com.imprimelibros.erp.payments.repo.PaymentTransactionRepository;
|
||||
import com.imprimelibros.erp.payments.repo.RefundRepository;
|
||||
import com.imprimelibros.erp.redsys.RedsysService;
|
||||
import com.imprimelibros.erp.redsys.RedsysService.FormPayload;
|
||||
import com.imprimelibros.erp.redsys.RedsysService.Notification;
|
||||
import com.imprimelibros.erp.redsys.RedsysService.PaymentRequest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class PaymentService {
|
||||
|
||||
private final PaymentRepository payRepo;
|
||||
private final PaymentTransactionRepository txRepo;
|
||||
private final RefundRepository refundRepo;
|
||||
private final RedsysService redsysService;
|
||||
private final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
@Autowired
|
||||
public PaymentService(PaymentRepository payRepo,
|
||||
PaymentTransactionRepository txRepo,
|
||||
RefundRepository refundRepo,
|
||||
RedsysService redsysService) {
|
||||
this.payRepo = payRepo;
|
||||
this.txRepo = txRepo;
|
||||
this.refundRepo = refundRepo;
|
||||
this.redsysService = redsysService;
|
||||
}
|
||||
|
||||
/** Crea Payment y devuelve form auto-submit Redsys. Ds_Order = 12 dígitos con el ID. */
|
||||
@Transactional
|
||||
public FormPayload createRedsysPayment(Long orderId, long amountCents, String currency) throws Exception {
|
||||
Payment p = new Payment();
|
||||
p.setOrderId(orderId);
|
||||
p.setCurrency(currency);
|
||||
p.setAmountTotalCents(amountCents);
|
||||
p.setGateway("redsys");
|
||||
p.setStatus(PaymentStatus.REQUIRES_PAYMENT_METHOD);
|
||||
p = payRepo.saveAndFlush(p);
|
||||
|
||||
String dsOrder = String.format("%012d", p.getId());
|
||||
p.setGatewayOrderId(dsOrder);
|
||||
payRepo.save(p);
|
||||
|
||||
PaymentRequest req = new PaymentRequest(dsOrder, amountCents, "Compra en Imprimelibros", "card");
|
||||
return redsysService.buildRedirectForm(req);
|
||||
}
|
||||
|
||||
/** Procesa notificación Redsys (ok/notify). Idempotente. */
|
||||
@Transactional
|
||||
public void handleRedsysNotification(String dsSignature, String dsMerchantParameters) throws Exception {
|
||||
Notification notif = redsysService.validateAndParseNotification(dsSignature, dsMerchantParameters);
|
||||
|
||||
Payment p = payRepo.findByGatewayAndGatewayOrderId("redsys", notif.getOrder())
|
||||
.orElseThrow(() -> new IllegalStateException("Payment no encontrado para Ds_Order " + notif.getOrder()));
|
||||
|
||||
if (!Objects.equals(p.getCurrency(), notif.getCurrency()))
|
||||
throw new IllegalStateException("Divisa inesperada");
|
||||
if (!Objects.equals(p.getAmountTotalCents(), notif.getAmountCents()))
|
||||
throw new IllegalStateException("Importe inesperado");
|
||||
|
||||
// ¿Ya registrado? Si ya capturaste, no repitas.
|
||||
if (p.getStatus() == PaymentStatus.CAPTURED || p.getStatus() == PaymentStatus.PARTIALLY_REFUNDED || p.getStatus() == PaymentStatus.REFUNDED) {
|
||||
return; // idempotencia simple a nivel Payment
|
||||
}
|
||||
|
||||
PaymentTransaction tx = new PaymentTransaction();
|
||||
tx.setPayment(p);
|
||||
tx.setType(PaymentTransactionType.CAPTURE);
|
||||
tx.setCurrency(p.getCurrency());
|
||||
tx.setAmountCents(notif.getAmountCents());
|
||||
tx.setStatus(notif.isAuthorized() ? PaymentTransactionStatus.SUCCEEDED : PaymentTransactionStatus.FAILED);
|
||||
// En Redsys el authorization code suele estar en Ds_AuthorisationCode
|
||||
Object authCode = notif.getRaw().get("Ds_AuthorisationCode");
|
||||
tx.setGatewayTransactionId(authCode != null ? String.valueOf(authCode) : null);
|
||||
tx.setGatewayResponseCode(notif.getResponse());
|
||||
tx.setResponsePayload(om.writeValueAsString(notif.getRaw()));
|
||||
tx.setProcessedAt(LocalDateTime.now());
|
||||
txRepo.save(tx);
|
||||
|
||||
if (notif.isAuthorized()) {
|
||||
p.setAuthorizationCode(tx.getGatewayTransactionId());
|
||||
p.setStatus(PaymentStatus.CAPTURED);
|
||||
p.setAmountCapturedCents(p.getAmountCapturedCents() + notif.getAmountCents());
|
||||
p.setAuthorizedAt(LocalDateTime.now());
|
||||
p.setCapturedAt(LocalDateTime.now());
|
||||
} else {
|
||||
p.setStatus(PaymentStatus.FAILED);
|
||||
p.setFailedAt(LocalDateTime.now());
|
||||
}
|
||||
payRepo.save(p);
|
||||
}
|
||||
|
||||
/** Refund (simulado a nivel pasarela; actualiza BD). Sustituye gatewayRefundId por el real cuando lo tengas. */
|
||||
@Transactional
|
||||
public void refundViaRedsys(Long paymentId, long amountCents, String idempotencyKey) {
|
||||
Payment p = payRepo.findById(paymentId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Payment no encontrado"));
|
||||
|
||||
if (amountCents <= 0) throw new IllegalArgumentException("Importe inválido");
|
||||
long maxRefundable = p.getAmountCapturedCents() - p.getAmountRefundedCents();
|
||||
if (amountCents > maxRefundable) throw new IllegalStateException("Importe de devolución supera lo capturado");
|
||||
|
||||
txRepo.findByIdempotencyKey(idempotencyKey)
|
||||
.ifPresent(t -> { throw new IllegalStateException("Reembolso ya procesado"); });
|
||||
|
||||
Refund r = new Refund();
|
||||
r.setPayment(p);
|
||||
r.setAmountCents(amountCents);
|
||||
r.setStatus(RefundStatus.PENDING);
|
||||
r.setRequestedAt(LocalDateTime.now());
|
||||
r = refundRepo.save(r);
|
||||
|
||||
String gatewayRefundId = "REF-" + UUID.randomUUID(); // TODO: sustituir por el ID real de Redsys si usas su canal de devoluciones
|
||||
|
||||
PaymentTransaction tx = new PaymentTransaction();
|
||||
tx.setPayment(p);
|
||||
tx.setType(PaymentTransactionType.REFUND);
|
||||
tx.setStatus(PaymentTransactionStatus.SUCCEEDED);
|
||||
tx.setAmountCents(amountCents);
|
||||
tx.setCurrency(p.getCurrency());
|
||||
tx.setGatewayTransactionId(gatewayRefundId);
|
||||
tx.setIdempotencyKey(idempotencyKey);
|
||||
tx.setProcessedAt(LocalDateTime.now());
|
||||
txRepo.save(tx);
|
||||
|
||||
r.setStatus(RefundStatus.SUCCEEDED);
|
||||
r.setTransaction(tx);
|
||||
r.setGatewayRefundId(gatewayRefundId);
|
||||
r.setProcessedAt(LocalDateTime.now());
|
||||
refundRepo.save(r);
|
||||
|
||||
p.setAmountRefundedCents(p.getAmountRefundedCents() + amountCents);
|
||||
if (p.getAmountRefundedCents().equals(p.getAmountCapturedCents())) {
|
||||
p.setStatus(PaymentStatus.REFUNDED);
|
||||
} else {
|
||||
p.setStatus(PaymentStatus.PARTIALLY_REFUNDED);
|
||||
}
|
||||
payRepo.save(p);
|
||||
}
|
||||
|
||||
/** Transferencia bancaria: crea Payment en espera de ingreso. */
|
||||
@Transactional
|
||||
public Payment createBankTransferPayment(Long orderId, long amountCents, String currency) {
|
||||
Payment p = new Payment();
|
||||
p.setOrderId(orderId);
|
||||
p.setCurrency(currency);
|
||||
p.setAmountTotalCents(amountCents);
|
||||
p.setGateway("bank_transfer");
|
||||
p.setStatus(PaymentStatus.REQUIRES_ACTION);
|
||||
return payRepo.save(p);
|
||||
}
|
||||
|
||||
/** Marca transferencia como conciliada (capturada). */
|
||||
@Transactional
|
||||
public void markBankTransferAsCaptured(Long paymentId) {
|
||||
Payment p = payRepo.findById(paymentId).orElseThrow();
|
||||
if (!"bank_transfer".equals(p.getGateway())) throw new IllegalStateException("No es transferencia");
|
||||
p.setAmountCapturedCents(p.getAmountTotalCents());
|
||||
p.setCapturedAt(LocalDateTime.now());
|
||||
p.setStatus(PaymentStatus.CAPTURED);
|
||||
payRepo.save(p);
|
||||
|
||||
PaymentTransaction tx = new PaymentTransaction();
|
||||
tx.setPayment(p);
|
||||
tx.setType(PaymentTransactionType.CAPTURE);
|
||||
tx.setStatus(PaymentTransactionStatus.SUCCEEDED);
|
||||
tx.setAmountCents(p.getAmountTotalCents());
|
||||
tx.setCurrency(p.getCurrency());
|
||||
tx.setProcessedAt(LocalDateTime.now());
|
||||
txRepo.save(tx);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user