// Chromis POS - The New Face of Open Source POS // Copyright (c) (c) 2015-2016 // http://www.chromis.co.uk // // This file is part of Chromis POS // // Chromis POS 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. // // Chromis POS 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 Chromis POS. If not, see <http://www.gnu.org/licenses/>. /** * <p>Title: AIM Java Version 1.4.1_02-b06</p> * <p>Description: Advanced Integration Method</p> * <p>Copyright: Copyright (c) 2003</p> * <p>Company: Authorize.Net</p> * @author Authorize.Net * @version 3.1 */ /** * Based on sample code and snippets provided by: * Patrick Phelan, phelan@choicelogic.com * Roedy Green, Canadian Mind Products * Authorize.net - //github.com/AuthorizeNet */ // Modifications by Adrian Romero & Mikel Irurita package uk.chromis.pos.payment; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.HashMap; import java.util.Map; 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; import uk.chromis.data.loader.LocalRes; import uk.chromis.pos.forms.AppConfig; import uk.chromis.pos.forms.AppLocal; import uk.chromis.pos.forms.AppProperties; import uk.chromis.pos.util.AltEncrypter; /** * * */ public class PaymentGatewayAuthorizeNet implements PaymentGateway { private static String ENDPOINTADDRESS; private static final String OPERATIONVALIDATE = "AUTH_CAPTURE"; private static final String OPERATIONREFUND = "CREDIT"; private static final String APPROVED = "1"; private String m_sCommerceID; private String m_sCommercePassword; private boolean m_bTestMode; /** Creates a new instance of PaymentGatewayAuthorizeNet * @param props */ public PaymentGatewayAuthorizeNet() { // Grab some configuration variables m_sCommerceID = AppConfig.getInstance().getProperty("payment.commerceid"); AltEncrypter cypher = new AltEncrypter("cypherkey" + AppConfig.getInstance().getProperty("payment.commerceid")); this.m_sCommercePassword = cypher.decrypt(AppConfig.getInstance().getProperty("payment.commercepassword").substring(6)); m_bTestMode = Boolean.parseBoolean(AppConfig.getInstance().getProperty("payment.testmode")); ENDPOINTADDRESS = (m_bTestMode) ? "https://test.authorize.net/gateway/transact.dll" : "https://cardpresent.authorize.net/gateway/transact.dll"; } /** * * @param payinfo */ @Override public void execute(PaymentInfoMagcard payinfo) { StringBuilder sb = new StringBuilder(); try { sb.append("x_cpversion=1.0"); // current supported version sb.append("&x_market_type=2"); // 2 = Retail sb.append("&x_device_type=5"); //5 = PC based terminal sb.append("&x_login="); sb.append(URLEncoder.encode(m_sCommerceID, "UTF-8")); sb.append("&x_tran_key="); sb.append(URLEncoder.encode(m_sCommercePassword, "UTF-8")); sb.append("&x_amount="); NumberFormat formatter = new DecimalFormat("0000.00"); String amount = formatter.format(Math.abs(payinfo.getTotal())); sb.append(URLEncoder.encode(amount.replace(',', '.'), "UTF-8")); if (payinfo.getTrack1(true) == null) { sb.append("&x_card_num="); sb.append(URLEncoder.encode(payinfo.getCardNumber(), "UTF-8")); sb.append("&x_exp_date="); String tmp = payinfo.getExpirationDate(); sb.append(URLEncoder.encode(tmp, "UTF-8")); String[] cc_name = payinfo.getHolderName().split(" "); sb.append("&x_first_name="); if (cc_name.length > 0) { sb.append(URLEncoder.encode(cc_name[0], "UTF-8")); } sb.append("&x_last_name="); if (cc_name.length > 1) { sb.append(URLEncoder.encode(cc_name[1], "UTF-8")); } } else { // Example Track1 // %B4111111111111111^PADILLA VISDOMINE/LUIS^0905123000000000000002212322222?5 sb.append("&x_track1="); sb.append(payinfo.getTrack1(false)); sb.append("&x_track2="); sb.append(payinfo.getTrack2(false)); } sb.append("&x_method=CC"); sb.append("&x_version=3.1"); sb.append("&x_repsonse_format=0"); sb.append("&x_delim_char=|"); sb.append("&x_relay_response=FALSE"); sb.append("&x_test_request="); sb.append(m_bTestMode); //PAYMENT if (payinfo.getTotal() >= 0.0) { sb.append("&x_type="); sb.append(OPERATIONVALIDATE); //sb.append("&x_card_code=340"); //CCV } //REFUND else { sb.append("&x_type="); sb.append(OPERATIONREFUND); sb.append("&x_trans_id="); sb.append(payinfo.getTransactionID()); } // 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 connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); try (DataOutputStream out = new DataOutputStream(connection.getOutputStream())) { out.write(sb.toString().getBytes()); out.flush(); out.close(); } String returned; try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { returned = in.readLine(); in.close(); } AuthorizeNetParser anp = new AuthorizeNetParser(returned); Map props = anp.splitXML(); if (anp.getResult().equals(LocalRes.getIntString("button.ok"))) { if (APPROVED.equals(props.get("ResponseCode"))) { //Transaction approved // payinfo.paymentOK((String) props.get("AuthCode"), (String) props.get("TransID"), returned); payinfo.paymentOK(props.get("AuthCode").toString(), props.get("TransID").toString(), returned); } else { StringBuilder errorLine = new StringBuilder(); //Transaction declined if (anp.getNumErrors()>0) { for (int i=1; i<=anp.getNumErrors(); i++) { errorLine.append(props.get("ErrorCode"+Integer.toString(i))); errorLine.append(": "); errorLine.append(props.get("ErrorText"+Integer.toString(i))); errorLine.append("\n"); } payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), errorLine.toString()); } } } else { payinfo.paymentError(anp.getResult(), ""); } } catch (UnsupportedEncodingException | MalformedURLException eUE) { payinfo.paymentError(AppLocal.getIntString("message.paymentexceptionservice"), eUE.getMessage()); } catch(IOException e){ payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), e.getMessage()); } } private class AuthorizeNetParser extends DefaultHandler { private SAXParser m_sp = null; private final Map props = new HashMap(); private String text; private final InputStream is; private String result; private int numMessages = 0; private int numErrors = 0; public AuthorizeNetParser(String input) { is = new ByteArrayInputStream(input.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 { if (qName.equals("ResponseCode")) { props.put("ResponseCode", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("ErrorCode")){ numErrors++; props.put("ErrorCode"+Integer.toString(numErrors), URLDecoder.decode(text, "UTF-8")); text = ""; } else if (qName.equals("ErrorText")) { props.put("ErrorText"+Integer.toString(numErrors), URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("Code")) { numMessages++; props.put("Code"+Integer.toString(numMessages), URLDecoder.decode(text, "UTF-8")); text = ""; } else if (qName.equals("Description")) { props.put("Description"+Integer.toString(numMessages), URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("AuthCode")) { props.put("AuthCode", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("AVSResultCode")) { props.put("AVSResultCode", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("CVVResultCode")) { props.put("CVVResultCode", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("TransID")) { props.put("TransID", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("RefTransID")) { props.put("RefTransID", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("TransHash")) { props.put("TransHash", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("TestMode")) { props.put("TestMode", URLDecoder.decode(text, "UTF-8")); text=""; } else if (qName.equals("UserRef")) { props.put("UserRef", URLDecoder.decode(text, "UTF-8")); text=""; } } 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; } public int getNumErrors(){ return numErrors; } public int getNumMessages(){ return numMessages; } } }