From ca0c6b2181c3fc569d08767a17970e9322d45318 Mon Sep 17 00:00:00 2001 From: Hugh Date: Sat, 6 Jun 2026 14:47:39 +0100 Subject: [PATCH] UOCL-310: payment happy path --- .gitignore | 1 + pom.xml | 17 ++- .../pos/config/JPanelConfigPayment.java | 43 ++++++-- .../pos/payment/PaymentGatewayFac.java | 2 + .../payment/PaymentGatewayMediaPayments.java | 98 ++++++++++++++++++ test.jpg | Bin 0 -> 12606 bytes 6 files changed, 144 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/unicenta/pos/payment/PaymentGatewayMediaPayments.java create mode 100644 test.jpg diff --git a/.gitignore b/.gitignore index a33ed9d..71b25b6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ docker.sock .factorypath .settings/ .project +AGENTS.md diff --git a/pom.xml b/pom.xml index 18698e6..a152445 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,6 @@ 4.0.0 - com.unicenta unicentaopos 5.4.0 @@ -116,7 +115,6 @@ poi 3.17 - org.apache.poi poi-ooxml @@ -181,7 +179,6 @@ slf4j-api - org.swinglabs.swingx @@ -255,11 +252,11 @@ derbytools ${derby.version} - - - - - + + + + + xml-apis xml-apis @@ -293,7 +290,6 @@ DynamicJasper-core-fonts 2.0 - org.eclipse.jdt.core.compiler ecj @@ -361,7 +357,7 @@ com.unicenta unicenta-plugins - 1.3.1 + 1.3.2-SNAPSHOT com.gluonhq @@ -450,7 +446,6 @@ 4.2.0 test - diff --git a/src/main/java/com/unicenta/pos/config/JPanelConfigPayment.java b/src/main/java/com/unicenta/pos/config/JPanelConfigPayment.java index dfd6084..ea12ec6 100644 --- a/src/main/java/com/unicenta/pos/config/JPanelConfigPayment.java +++ b/src/main/java/com/unicenta/pos/config/JPanelConfigPayment.java @@ -53,6 +53,7 @@ public class JPanelConfigPayment extends javax.swing.JPanel implements PanelConf initPayments("external", new ConfigPaymentPanelEmpty()); initPayments("PaymentSense", new ConfigPaymentPanelEmpty()); initPayments("Teya", new ConfigPaymentPanelEmpty()); + initPayments("MediaPayments", new ConfigPaymentPanelEmpty()); // Lector de tarjetas. jcboCardReader.addItem("Not defined"); @@ -91,9 +92,17 @@ public class JPanelConfigPayment extends javax.swing.JPanel implements PanelConf jcboCardReader.setSelectedItem(config.getProperty("payment.magcardreader")); jcboPaymentGateway.setSelectedItem(config.getProperty("payment.gateway")); - urlValue.setText(config.getProperty("payment.URL")); - deviceIdValue.setText(config.getProperty("payment.device-id")); - securityKeyValue.setText(config.getProperty("payment.security-key")); + + String gateway = comboValue(jcboPaymentGateway.getSelectedItem()); + if ("MediaPayments".equals(gateway)) { + urlValue.setText(config.getProperty("media-payments.pdq")); + deviceIdValue.setText(config.getProperty("media-payments.api-key")); + securityKeyValue.setText(config.getProperty("media-payments.secret")); + } else { + urlValue.setText(config.getProperty("payment.URL")); + deviceIdValue.setText(config.getProperty("payment.device-id")); + securityKeyValue.setText(config.getProperty("payment.security-key")); + } paymentConfiguration.loadProperties(config); dirty.setDirty(false); } @@ -107,9 +116,17 @@ public class JPanelConfigPayment extends javax.swing.JPanel implements PanelConf config.setProperty("payment.magcardreader", comboValue(jcboCardReader.getSelectedItem())); config.setProperty("payment.gateway", comboValue(jcboPaymentGateway.getSelectedItem())); - config.setProperty("payment.URL", urlValue.getText()); - config.setProperty("payment.device-id", deviceIdValue.getText()); - config.setProperty("payment.security-key", securityKeyValue.getText()); + + String gateway = comboValue(jcboPaymentGateway.getSelectedItem()); + if ("MediaPayments".equals(gateway)) { + config.setProperty("media-payments.pdq", urlValue.getText()); + config.setProperty("media-payments.api-key", deviceIdValue.getText()); + config.setProperty("media-payments.secret", securityKeyValue.getText()); + } else { + config.setProperty("payment.URL", urlValue.getText()); + config.setProperty("payment.device-id", deviceIdValue.getText()); + config.setProperty("payment.security-key", securityKeyValue.getText()); + } paymentConfiguration.saveProperties(config); dirty.setDirty(false); } @@ -275,6 +292,9 @@ public class JPanelConfigPayment extends javax.swing.JPanel implements PanelConf paymentConfiguration = paymentsName.get(comboValue(jcboPaymentGateway.getSelectedItem())); if (comboValue(jcboPaymentGateway.getSelectedItem()).equals("PaymentSense")) { + urlLabel.setText("URL"); + deviceIdLabel.setText("Device ID"); + securityKeyLabel.setText("Security Key"); emvConfigPanel.setVisible(Boolean.TRUE); jcboCardReader.setSelectedItem("EMV"); jcboCardReader.setEnabled(Boolean.FALSE); @@ -285,7 +305,18 @@ public class JPanelConfigPayment extends javax.swing.JPanel implements PanelConf jcboCardReader.setSelectedItem("EMV"); jcboCardReader.setEnabled(Boolean.FALSE); } + else if (comboValue(jcboPaymentGateway.getSelectedItem()).equals("MediaPayments")) { + urlLabel.setText("Terminal ID"); + deviceIdLabel.setText("API Key"); + securityKeyLabel.setText("Security Key"); + emvConfigPanel.setVisible(Boolean.TRUE); + jcboCardReader.setSelectedItem("EMV"); + jcboCardReader.setEnabled(Boolean.FALSE); + } else { + urlLabel.setText("URL"); + deviceIdLabel.setText("Device ID"); + securityKeyLabel.setText("Security Key"); emvConfigPanel.setVisible(Boolean.FALSE); jcboCardReader.setEnabled(Boolean.TRUE); } diff --git a/src/main/java/com/unicenta/pos/payment/PaymentGatewayFac.java b/src/main/java/com/unicenta/pos/payment/PaymentGatewayFac.java index c0a8f55..2c27bf7 100644 --- a/src/main/java/com/unicenta/pos/payment/PaymentGatewayFac.java +++ b/src/main/java/com/unicenta/pos/payment/PaymentGatewayFac.java @@ -41,6 +41,8 @@ public class PaymentGatewayFac { return new PaymentGatewayPaymentSense(); case "Teya": return new PaymentGatewayTeya(); + case "MediaPayments": + return new PaymentGatewayMediaPayments(); default: return null; } diff --git a/src/main/java/com/unicenta/pos/payment/PaymentGatewayMediaPayments.java b/src/main/java/com/unicenta/pos/payment/PaymentGatewayMediaPayments.java new file mode 100644 index 0000000..72339cb --- /dev/null +++ b/src/main/java/com/unicenta/pos/payment/PaymentGatewayMediaPayments.java @@ -0,0 +1,98 @@ +// uniCenta oPOS - Touch Friendly Point Of Sale +// Copyright (c) 2009-2018 uniCenta & previous Openbravo POS works +// https://unicenta.com +// +// This file is part of uniCenta oPOS +// +// uniCenta oPOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// uniCenta oPOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with uniCenta oPOS. If not, see . + +package com.unicenta.pos.payment; + +import com.unicenta.plugins.Application; +import com.unicenta.plugins.common.AppContext; +import com.unicenta.pos.util.RoundUtils; +import lombok.extern.slf4j.Slf4j; + +import java.awt.*; +import java.util.ArrayList; + +@Slf4j +public class PaymentGatewayMediaPayments implements PaymentGateway { + + public PaymentGatewayMediaPayments() { + } + + Window getPaymentWindow(double amount) { + if (amount > 0) { + ArrayList paymentWindows = new ArrayList<>(); + for (Window window : Window.getWindows()) { + if (window instanceof JPaymentSelectReceipt) { + paymentWindows.add((JPaymentSelectReceipt) window); + } + } + return paymentWindows.get(paymentWindows.size()-1); + } + else { + ArrayList paymentWindows = new ArrayList<>(); + for (Window window : Window.getWindows()) { + if (window instanceof JPaymentSelectRefund) { + paymentWindows.add((JPaymentSelectRefund) window); + } + } + return paymentWindows.get(paymentWindows.size()-1); + } + } + + @Override + public void execute(PaymentInfoMagcard payinfo) { + + int timer = 0; + int timeout = 180; + + double roundedValue = RoundUtils.round(payinfo.getTotal()); + new Application().mediaPaymentsTransaction(roundedValue, getPaymentWindow(roundedValue)); + + while (AppContext.getIsProcessing() == null || AppContext.getIsProcessing()) { + try { + log.info("uniCenta-oPos: waiting for payment to complete ...."); + Thread.sleep(1000); + timer += 1; + if (timer > timeout) break; + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + } + + if (AppContext.getMediaPaymentsPaymentResult() == null) { + payinfo.paymentError("Transaction Error! Please try again", "No Response"); + } + else if ("APPROVED".equals(AppContext.getMediaPaymentsPaymentResult().getProcessingDetails().getStatus())) { + payinfo.setCardName(AppContext.getMediaPaymentsPaymentResult().getProcessingDetails().getCard().getType()); + payinfo.setVerification(AppContext.getMediaPaymentsPaymentResult().getProcessingDetails().getVerificationMethod()); + payinfo.setChipAndPin(true); + payinfo.paymentOK( + AppContext.getMediaPaymentsPaymentResult().getAdditionalInformation().getRequestId(), + AppContext.getMediaPaymentsPaymentResult().getTransactionDetails().getId(), + AppContext.getMediaPaymentsPaymentResult().getProcessingDetails().getStatus() + ); + } + else { + String errorMsg = AppContext.getMediaPaymentsPaymentResult().getErrorMessage(); + if (errorMsg == null) { + errorMsg = AppContext.getMediaPaymentsPaymentResult().getMessage(); + } + payinfo.paymentError("Transaction Error! Please try again", errorMsg); + } + } +} diff --git a/test.jpg b/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..13f36189b1fbf3bbbd3237672d6022e81934a1e4 GIT binary patch literal 12606 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 literal 0 HcmV?d00001