// uniCenta oPOS - Touch Friendly Point Of Sale // Copyright (C) 2008-2009 Openbravo, S.L. // http://www.unicenta.net/unicentaopos // // 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 <http://www.gnu.org/licenses/>. package com.openbravo.pos.payment; import com.openbravo.data.loader.LocalRes; import com.openbravo.pos.forms.AppLocal; import com.openbravo.pos.forms.AppProperties; import com.openbravo.pos.util.AltEncrypter; import com.openbravo.pos.util.StringUtils; import java.io.*; import java.net.*; import java.security.MessageDigest; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.*; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * * @author Mikel Irurita */ public class PaymentGatewayCaixa implements PaymentGateway { private static String ENDPOINTADDRESS; private static final String SALE = "A"; private static final String REFUND = "3"; private static String SALEAPPROVED = "0000"; private static String REFUNDAPPROVED = "0900"; private String m_sCurrency; private String sMerchantCode; private String sTerminal; private String sCommerceSign; private boolean bSha; private boolean m_bTestMode; public PaymentGatewayCaixa (AppProperties props) { AltEncrypter cypher = new AltEncrypter("cypherkey"); this.sCommerceSign = cypher.decrypt(props.getProperty("payment.commercesign").substring(6)); this.m_bTestMode = Boolean.valueOf(props.getProperty("payment.testmode")).booleanValue(); //EUR, USD, GPB this.m_sCurrency = (Locale.getDefault().getCountry().isEmpty()) ? Currency.getInstance("EUR").getCurrencyCode() : Currency.getInstance(Locale.getDefault()).getCurrencyCode(); this.sTerminal = props.getProperty("payment.terminal"); this.sMerchantCode = props.getProperty("payment.commerceid"); this.bSha = Boolean.valueOf(props.getProperty("payment.SHA")).booleanValue(); ENDPOINTADDRESS = (m_bTestMode) ? "https://sis-t.sermepa.es:25443/sis/operaciones" : "https://sis.sermepa.es/sis/realizarPago"; } public PaymentGatewayCaixa(){ } private String createOrderId() { Random r = new Random(); NumberFormat nf = new DecimalFormat("0000000000"); return nf.format( Math.abs(r.nextInt()) + (Math.abs(System.currentTimeMillis()) % 1000000) ); } @Override public void execute(PaymentInfoMagcard payinfo) { //merchantCode = "999008881"; //terminal = "1"; //sign = "qwertyasdf0123456789"; // JG 16 May 12 use StringBuilder in place of StringBuilder StringBuilder sb = new StringBuilder(); String currency = "978"; //default euros String xml=""; // JG 16 May 12 use switch switch (m_sCurrency) { case "USD": currency = "840"; //dollars break; case "GPD": currency = "826"; break; } NumberFormat nf = new DecimalFormat("00"); String amount = nf.format( Math.abs(payinfo.getTotal())*100 ); String orderid = createOrderId(); try { if (payinfo.getTotal() > 0.0) { String firma = amount + orderid + sMerchantCode + currency + payinfo.getCardNumber() + SALE + sCommerceSign; xml = "<DATOSENTRADA>" + "<DS_Version>0.1</DS_Version>" + "<DS_MERCHANT_AMOUNT>"+ amount +"</DS_MERCHANT_AMOUNT>" + "<DS_MERCHANT_CURRENCY>"+currency+"</DS_MERCHANT_CURRENCY>" + "<DS_MERCHANT_ORDER>"+ orderid +"</DS_MERCHANT_ORDER>" + "<DS_MERCHANT_MERCHANTCODE>"+ sMerchantCode +"</DS_MERCHANT_MERCHANTCODE>" + "<DS_MERCHANT_MERCHANTURL></DS_MERCHANT_MERCHANTURL>" + //"<DS_MERCHANT_MERCHANTNAME>sample merchant</DS_MERCHANT_MERCHANTNAME>" + //Optional "<DS_MERCHANT_MERCHANTSIGNATURE>"+getSHA1(firma)+"</DS_MERCHANT_MERCHANTSIGNATURE>" + "<DS_MERCHANT_TERMINAL>"+ sTerminal +"</DS_MERCHANT_TERMINAL>" + "<DS_MERCHANT_TRANSACTIONTYPE>"+SALE+"</DS_MERCHANT_TRANSACTIONTYPE>" + //"<DS_MERCHANT_MERCHANTDATA>sample data</DS_MERCHANT_MERCHANTDATA>" + //Optional "<DS_MERCHANT_PAN>"+ payinfo.getCardNumber() +"</DS_MERCHANT_PAN>" + "<DS_MERCHANT_EXPIRYDATE>"+ payinfo.getExpirationDate() +"</DS_MERCHANT_EXPIRYDATE>" + //"<DS_MERCHANT_CVV2>sample cvv</DS_MERCHANT_CVV2>" + //Optional "</DATOSENTRADA>"; } else { String firma = amount + payinfo.getTransactionID() + sMerchantCode + currency + REFUND + sCommerceSign; xml = "<DATOSENTRADA>" + "<DS_Version>0.1</DS_Version>" + "<DS_MERCHANT_AMOUNT>"+ amount +"</DS_MERCHANT_AMOUNT>" + "<DS_MERCHANT_CURRENCY>"+currency+"</DS_MERCHANT_CURRENCY>" + "<DS_MERCHANT_ORDER>"+ payinfo.getTransactionID() +"</DS_MERCHANT_ORDER>" + "<DS_MERCHANT_MERCHANTCODE>"+ sMerchantCode +"</DS_MERCHANT_MERCHANTCODE>" + "<DS_MERCHANT_MERCHANTURL></DS_MERCHANT_MERCHANTURL>" + "<DS_MERCHANT_MERCHANTSIGNATURE>"+getSHA1(firma)+"</DS_MERCHANT_MERCHANTSIGNATURE>" + "<DS_MERCHANT_TERMINAL>"+ sTerminal +"</DS_MERCHANT_TERMINAL>" + "<DS_MERCHANT_TRANSACTIONTYPE>"+REFUND+"</DS_MERCHANT_TRANSACTIONTYPE>" + "</DATOSENTRADA>"; } // JG 16 May 12 use chain sb.append("entrada=").append(URLEncoder.encode(xml, "UTF-8")); // open secure connection URL url = new URL(ENDPOINTADDRESS); URLConnection connection = url.openConnection(); connection.setDoOutput(true); connection.setUseCaches(false); // not necessarily required but fixes a bug with some servers // JG 16 May 12 use try-with-resources connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) { out.write(sb.toString().getBytes()); out.flush(); } String sReturned; // JG 16 May 12 use try-with-resources try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { sReturned = in.readLine(); } if (sReturned == null) { payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Response empty."); } else { LaCaixaParser lpp = new LaCaixaParser(sReturned); Map props = lpp.splitXML(); if (lpp.getResult().equals(LocalRes.getIntString("button.ok"))) { //printResponse(props); if (SALEAPPROVED.equals(props.get("Ds_Response")) || REFUNDAPPROVED.equals(props.get("Ds_Response"))) { //Transaction approved payinfo.paymentOK((String) props.get("Ds_AuthorisationCode"), (String) props.get("Ds_Order"), sReturned); } else { String sCode = (String) props.get("Ds_Response"); // JG 16 May 12 use switch switch (sCode) { case "0101": payinfo.paymentError(AppLocal.getIntString("message.paymentnotauthorised"), "Card date expired"); break; case "0102": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Tarjeta en excepción transitoria o bajo sospecha de fraude."); break; case "0104": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Operación no permitida para esa tarjeta o terminal."); break; case "0116": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Disponible insuficiente."); break; case "0118": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Tarjeta no registrada."); break; case "0129": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "CVV2 security code invalid. Amount not supplied or invalid."); break; case "0180": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Tarjeta ajena al servicio."); break; case "0184": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Cardholder authentication error."); break; case "0190": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Denegation of service without reason."); break; case "0191": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Expiry date invalid."); break; case "0202": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta."); break; case "0904": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Comercio no registrado en FUC."); break; case "9912": case "912": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Emisor no disponible."); break; default: payinfo.paymentError(AppLocal.getIntString("message.paymenterrorunknown"), ""); break; } sCode = (String)props.get("CODIGO"); // JG 16 May 12 use switch switch (sCode) { case "SIS0054": payinfo.paymentError(AppLocal.getIntString("message.paymentnotauthorised"), "Pedido repetido."); break; case "SIS0078": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Método de pago no disponible para su tarjeta."); break; case "SIS0093": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Tarjeta no válida."); break; case "SIS0094": payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), "Error en la llamada al MPI sin controlar."); break; } } } else { payinfo.paymentError(lpp.getResult(), ""); } } // JG 16 May 12 use multicatch } catch (UnsupportedEncodingException | MalformedURLException eUE) { payinfo.paymentError(AppLocal.getIntString("message.paymentexceptionservice"), eUE.getMessage()); } catch(IOException e){ payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), e.getMessage()); } } public String getSHA1(String input){ byte[] output = null; try { MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(input.getBytes()); output = md.digest(); } catch (Exception e) { System.out.println("Exception: "+e); } return StringUtils.byte2hex(output); } public class LaCaixaParser extends DefaultHandler { private SAXParser m_sp = null; private Map props = new HashMap(); private String text; private InputStream is; private String result; public LaCaixaParser(String in) { is = new ByteArrayInputStream(in.getBytes()); } public Map splitXML(){ try { if (m_sp == null) { SAXParserFactory spf = SAXParserFactory.newInstance(); m_sp = spf.newSAXParser(); } m_sp.parse(is, this); } catch (ParserConfigurationException ePC) { result = LocalRes.getIntString("exception.parserconfig"); } catch (SAXException eSAX) { result = LocalRes.getIntString("exception.xmlfile"); } catch (IOException eIO) { result = LocalRes.getIntString("exception.iofile"); } result = LocalRes.getIntString("button.ok"); return props; } @Override public void endElement(String uri, String localName, String qName) throws SAXException { try { // JG 16 May 12 use switch switch (qName) { case "CODIGO": props.put("CODIGO", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Version": props.put("Ds_Version", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Amount": props.put("Ds_Amount", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Currency": props.put("Ds_Currency", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Order": props.put("Ds_Order", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Signature": props.put("Ds_Signature", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Terminal": props.put("Ds_Terminal", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Response": props.put("Ds_Response", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_AuthorisationCode": props.put("Ds_AuthorisationCode", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_TransactionType": props.put("Ds_TransactionType", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_SecurePayment": props.put("Ds_SecurePayment", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Language": props.put("Ds_Language", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_MerchantData": props.put("Ds_MerchantData", URLDecoder.decode(text, "UTF-8")); text=""; break; case "Ds_Card_Country": props.put("Ds_Card_Country", URLDecoder.decode(text, "UTF-8")); text=""; break; } } catch(UnsupportedEncodingException eUE){ result = eUE.getMessage(); } } @Override public void startDocument() throws SAXException { text = new String(); } @Override public void endDocument() throws SAXException { } @Override public void characters(char[] ch, int start, int length) throws SAXException { if (text!=null) { text = new String(ch, start, length); } } public String getResult(){ return this.result; } } }