package com.salesmanager.core.business.modules.integration.payment.impl; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.UUID; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.salesmanager.core.business.services.system.MerchantLogService; import com.salesmanager.core.business.utils.CreditCardUtils; import com.salesmanager.core.business.utils.ProductPriceUtils; import com.salesmanager.core.model.customer.Customer; import com.salesmanager.core.model.merchant.MerchantStore; import com.salesmanager.core.model.order.Order; import com.salesmanager.core.model.payments.CreditCardPayment; import com.salesmanager.core.model.payments.Payment; import com.salesmanager.core.model.payments.PaymentType; import com.salesmanager.core.model.payments.Transaction; import com.salesmanager.core.model.payments.TransactionType; import com.salesmanager.core.model.shoppingcart.ShoppingCartItem; import com.salesmanager.core.model.system.IntegrationConfiguration; import com.salesmanager.core.model.system.IntegrationModule; import com.salesmanager.core.model.system.MerchantLog; import com.salesmanager.core.model.system.ModuleConfig; import com.salesmanager.core.modules.integration.IntegrationException; import com.salesmanager.core.modules.integration.payment.model.PaymentModule; public class BeanStreamPayment implements PaymentModule { @Inject private ProductPriceUtils productPriceUtils; @Inject private MerchantLogService merchantLogService; private static final Logger LOGGER = LoggerFactory.getLogger(BeanStreamPayment.class); @Override public Transaction initTransaction(MerchantStore store, Customer customer, BigDecimal amount, Payment payment, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { // TODO Auto-generated method stub return null; } @Override public Transaction authorize(MerchantStore store, Customer customer, List<ShoppingCartItem> items, BigDecimal amount, Payment payment, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { return processTransaction(store, customer, TransactionType.AUTHORIZE, amount, payment, configuration, module); } @Override public Transaction capture(MerchantStore store, Customer customer, Order order, Transaction capturableTransaction, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { try { //authorize a preauth String trnID = capturableTransaction.getTransactionDetails().get("TRANSACTIONID"); String amnt = productPriceUtils.getAdminFormatedAmount(store, order.getTotal()); /** merchant_id=123456789&requestType=BACKEND &trnType=PAC&username=user1234&password=pass1234&trnID=1000 2115 --> requires also adjId [not documented] **/ StringBuilder messageString = new StringBuilder(); messageString.append("requestType=BACKEND&"); messageString.append("merchant_id=").append(configuration.getIntegrationKeys().get("merchantid")).append("&"); messageString.append("trnType=").append("PAC").append("&"); messageString.append("username=").append(configuration.getIntegrationKeys().get("username")).append("&"); messageString.append("password=").append(configuration.getIntegrationKeys().get("password")).append("&"); messageString.append("trnAmount=").append(amnt).append("&"); messageString.append("adjId=").append(trnID).append("&"); messageString.append("trnID=").append(trnID); LOGGER.debug("REQUEST SENT TO BEANSTREAM -> " + messageString.toString()); Transaction response = this.sendTransaction(null, store, messageString.toString(), "PAC", TransactionType.CAPTURE, PaymentType.CREDITCARD, order.getTotal(), configuration, module); return response; } catch(Exception e) { if(e instanceof IntegrationException) throw (IntegrationException)e; throw new IntegrationException("Error while processing BeanStream transaction",e); } } @Override public Transaction authorizeAndCapture(MerchantStore store, Customer customer, List<ShoppingCartItem> items, BigDecimal amount, Payment payment, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { return processTransaction( store, customer, TransactionType.AUTHORIZECAPTURE, amount, payment, configuration, module); } @Override public Transaction refund(boolean partial, MerchantStore store, Transaction transaction, Order order, BigDecimal amount, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { HttpURLConnection conn = null; try { boolean bSandbox = false; if (configuration.getEnvironment().equals("TEST")) {// sandbox bSandbox = true; } String server = ""; ModuleConfig configs = module.getModuleConfigs().get("PROD"); if (bSandbox == true) { configs = module.getModuleConfigs().get("TEST"); } if(configs==null) { throw new IntegrationException("Module not configured for TEST or PROD"); } server = new StringBuffer().append( configs.getScheme()).append("://") .append(configs.getHost()) .append(":") .append(configs.getPort()) .append(configs.getUri()).toString(); String trnID = transaction.getTransactionDetails().get("TRANSACTIONID"); String amnt = productPriceUtils.getAdminFormatedAmount(store, amount); /** merchant_id=123456789&requestType=BACKEND &trnType=R&username=user1234&password=pass1234 &trnOrderNumber=1234&trnAmount=1.00&adjId=1000 2115 **/ StringBuilder messageString = new StringBuilder(); messageString.append("requestType=BACKEND&"); messageString.append("merchant_id=").append(configuration.getIntegrationKeys().get("merchantid")).append("&"); messageString.append("trnType=").append("R").append("&"); messageString.append("username=").append(configuration.getIntegrationKeys().get("username")).append("&"); messageString.append("password=").append(configuration.getIntegrationKeys().get("password")).append("&"); messageString.append("trnOrderNumber=").append(transaction.getTransactionDetails().get("TRNORDERNUMBER")).append("&"); messageString.append("trnAmount=").append(amnt).append("&"); messageString.append("adjId=").append(trnID); LOGGER.debug("REQUEST SENT TO BEANSTREAM -> " + messageString.toString()); URL postURL = new URL(server.toString()); conn = (HttpURLConnection) postURL.openConnection(); Transaction response = this.sendTransaction(null, store, messageString.toString(), "R", TransactionType.REFUND, PaymentType.CREDITCARD, amount, configuration, module); return response; } catch(Exception e) { if(e instanceof IntegrationException) throw (IntegrationException)e; throw new IntegrationException("Error while processing BeanStream transaction",e); } finally { if (conn != null) { try { conn.disconnect(); } catch (Exception ignore) { // TODO: handle exception } } } } private Transaction sendTransaction( String orderNumber, MerchantStore store, String transaction, String beanstreamType, TransactionType transactionType, PaymentType paymentType, BigDecimal amount, IntegrationConfiguration configuration, IntegrationModule module ) throws IntegrationException { String agent = "Mozilla/4.0"; String respText = ""; Map<String,String> nvp = null; DataOutputStream output = null; DataInputStream in = null; BufferedReader is = null; HttpURLConnection conn =null; try { //transaction = "requestType=BACKEND&merchant_id=300200260&trnType=P&username=carlito&password=shopizer001&orderNumber=caa71106-7e3f-4975-a657-a35904dc32a0&trnCardOwner=Carl Samson&trnCardNumber=5100000020002000&trnExpMonth=10&trnExpYear=14&trnCardCvd=123&trnAmount=77.01&ordName=Carl S&ordAddress1=358 Du Languedoc&ordCity=Victoria&ordProvince=BC&ordPostalCode=V8T2E7&ordCountry=CA&ordPhoneNumber=(444) 555-6666&ordEmailAddress=csamson777@yahoo.com"; /** requestType=BACKEND&merchant_id=300200260 &trnType=P &username=carlito&password=shopizer001 &orderNumber=caa71106-7e3f-4975-a657-a35904dc32a0 &trnCardOwner=Carl Samson &trnCardNumber=5100000020002000 &trnExpMonth=10 &trnExpYear=14 &trnCardCvd=123 &trnAmount=77.01 &ordName=Carl S &ordAddress1=378 Du Languedoc &ordCity=Boucherville &ordProvince=QC &ordPostalCode=J4B8J9 &ordCountry=CA &ordPhoneNumber=(444) 555-6666 &ordEmailAddress=test@yahoo.com **/ /** merchant_id=123456789&requestType=BACKEND &trnType=P&trnOrderNumber=1234TEST&trnAmount=5.00&trnCardOwner=Joe+Test &trnCardNumber=4030000010001234 &trnExpMonth=10 &trnExpYear=16 &ordName=Joe+Test &ordAddress1=123+Test+Street &ordCity=Victoria &ordProvince=BC &ordCountry=CA &ordPostalCode=V8T2E7 &ordPhoneNumber=5555555555 &ordEmailAddress=joe%40testemail.com **/ boolean bSandbox = false; if (configuration.getEnvironment().equals("TEST")) {// sandbox bSandbox = true; } String server = ""; ModuleConfig configs = module.getModuleConfigs().get("PROD"); if (bSandbox == true) { configs = module.getModuleConfigs().get("TEST"); } if(configs==null) { throw new IntegrationException("Module not configured for TEST or PROD"); } server = new StringBuffer().append( configs.getScheme()).append("://") .append(configs.getHost()) .append(":") .append(configs.getPort()) .append(configs.getUri()).toString(); URL postURL = new URL(server.toString()); conn = (HttpURLConnection) postURL.openConnection(); // Set connection parameters. We need to perform input and output, // so set both as true. conn.setDoInput(true); conn.setDoOutput(true); // Set the content type we are POSTing. We impersonate it as // encoded form data conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("User-Agent", agent); conn.setRequestProperty("Content-Length", String .valueOf(transaction.length())); conn.setRequestMethod("POST"); // get the output stream to POST to. output = new DataOutputStream(conn.getOutputStream()); output.writeBytes(transaction); output.flush(); // Read input from the input stream. in = new DataInputStream(conn.getInputStream()); int rc = conn.getResponseCode(); if (rc != -1) { is = new BufferedReader(new InputStreamReader(conn .getInputStream())); String _line = null; while (((_line = is.readLine()) != null)) { respText = respText + _line; } LOGGER.debug("BeanStream response -> " + respText.trim()); nvp = formatUrlResponse(respText.trim()); } else { throw new IntegrationException("Invalid response from BeanStream, return code is " + rc); } //check //trnApproved=1&trnId=10003067&messageId=1&messageText=Approved&trnOrderNumber=E40089&authCode=TEST&errorType=N&errorFields= String transactionApproved = (String)nvp.get("TRNAPPROVED"); String transactionId = (String)nvp.get("TRNID"); String messageId = (String)nvp.get("MESSAGEID"); String messageText = (String)nvp.get("MESSAGETEXT"); String orderId = (String)nvp.get("TRNORDERNUMBER"); String authCode = (String)nvp.get("AUTHCODE"); String errorType = (String)nvp.get("ERRORTYPE"); String errorFields = (String)nvp.get("ERRORFIELDS"); if(!StringUtils.isBlank(orderNumber)) { nvp.put("INTERNALORDERID", orderNumber); } if(StringUtils.isBlank(transactionApproved)) { throw new IntegrationException("Required field transactionApproved missing from BeanStream response"); } //errors if(transactionApproved.equals("0")) { merchantLogService.save( new MerchantLog(store, "Can't process BeanStream message " + messageText + " return code id " + messageId)); IntegrationException te = new IntegrationException( "Can't process BeanStream message " + messageText); te.setExceptionType(IntegrationException.EXCEPTION_PAYMENT_DECLINED); te.setMessageCode("message.payment.beanstream." + messageId); te.setErrorCode(IntegrationException.TRANSACTION_EXCEPTION); throw te; } //create transaction object //return parseResponse(type,transaction,respText,nvp,order); return this.parseResponse(transactionType, paymentType, nvp, amount); } catch(Exception e) { if(e instanceof IntegrationException) { throw (IntegrationException)e; } throw new IntegrationException("Error while processing BeanStream transaction",e); } finally { if (is != null) { try { is.close(); } catch (Exception ignore) { // TODO: handle exception } } if (in != null) { try { in.close(); } catch (Exception ignore) { // TODO: handle exception } } if (output != null) { try { output.close(); } catch (Exception ignore) { // TODO: handle exception } } if (conn != null) { try { conn.disconnect(); } catch (Exception ignore) { // TODO: handle exception } } } } private Transaction processTransaction(MerchantStore store, Customer customer, TransactionType type, BigDecimal amount, Payment payment, IntegrationConfiguration configuration, IntegrationModule module) throws IntegrationException { boolean bSandbox = false; if (configuration.getEnvironment().equals("TEST")) {// sandbox bSandbox = true; } String server = ""; ModuleConfig configs = module.getModuleConfigs().get("PROD"); if (bSandbox == true) { configs = module.getModuleConfigs().get("TEST"); } if(configs==null) { throw new IntegrationException("Module not configured for TEST or PROD"); } server = new StringBuffer().append( configs.getScheme()).append("://") .append(configs.getHost()) .append(":") .append(configs.getPort()) .append(configs.getUri()).toString(); HttpURLConnection conn = null; try { String uniqueId = UUID.randomUUID().toString();//TODO String orderNumber = uniqueId; String amnt = productPriceUtils.getAdminFormatedAmount(store, amount); StringBuilder messageString = new StringBuilder(); String transactionType = "P"; if(type == TransactionType.AUTHORIZE) { transactionType = "PA"; } else if(type == TransactionType.AUTHORIZECAPTURE) { transactionType = "P"; } CreditCardPayment creditCardPayment = (CreditCardPayment)payment; messageString.append("requestType=BACKEND&"); messageString.append("merchant_id=").append(configuration.getIntegrationKeys().get("merchantid")).append("&"); messageString.append("trnType=").append(transactionType).append("&"); messageString.append("username=").append(configuration.getIntegrationKeys().get("username")).append("&"); messageString.append("password=").append(configuration.getIntegrationKeys().get("password")).append("&"); messageString.append("orderNumber=").append(orderNumber).append("&"); messageString.append("trnCardOwner=").append(creditCardPayment.getCardOwner()).append("&"); messageString.append("trnCardNumber=").append(creditCardPayment.getCreditCardNumber()).append("&"); messageString.append("trnExpMonth=").append(creditCardPayment.getExpirationMonth()).append("&"); messageString.append("trnExpYear=").append(creditCardPayment.getExpirationYear().substring(2)).append("&"); messageString.append("trnCardCvd=").append(creditCardPayment.getCredidCardValidationNumber()).append("&"); messageString.append("trnAmount=").append(amnt).append("&"); StringBuilder nm = new StringBuilder(); nm.append(customer.getBilling().getFirstName()).append(" ").append(customer.getBilling().getLastName()); messageString.append("ordName=").append(nm.toString()).append("&"); messageString.append("ordAddress1=").append(customer.getBilling().getAddress()).append("&"); messageString.append("ordCity=").append(customer.getBilling().getCity()).append("&"); String stateProvince = customer.getBilling().getState(); if(customer.getBilling().getZone()!=null) { stateProvince = customer.getBilling().getZone().getCode(); } String countryName = customer.getBilling().getCountry().getIsoCode(); messageString.append("ordProvince=").append(stateProvince).append("&"); messageString.append("ordPostalCode=").append(customer.getBilling().getPostalCode().replaceAll("\\s","")).append("&"); messageString.append("ordCountry=").append(countryName).append("&"); messageString.append("ordPhoneNumber=").append(customer.getBilling().getTelephone()).append("&"); messageString.append("ordEmailAddress=").append(customer.getEmailAddress()); /** * purchase (P) * ----------- REQUEST -> merchant_id=123456789&requestType=BACKEND&trnType=P&trnOrderNumber=1234TEST&trnAmount=5.00&trnCardOwner=Joe+Test&trnCardNumber=4030000010001234&trnExpMonth=10&trnExpYear=10&ordName=Joe+Test&ordAddress1=123+Test+Street&ordCity=Victoria&ordProvince=BC&ordCountry=CA&ordPostalCode=V8T2E7&ordPhoneNumber=5555555555&ordEmailAddress=joe%40testemail.com RESPONSE-> trnApproved=1&trnId=10003067&messageId=1&messageText=Approved&trnOrderNumber=E40089&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=10%2E00&trnDate=1%2F17%2F2008+11%3A36%3A34+AM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&rspCodeCav=0&rspCavResult=0&rspCodeCredit1=0&rspCodeCredit2=0&rspCodeCredit3=0&rspCodeCredit4=0&rspCodeAddr1=0&rspCodeAddr2=0&rspCodeAddr3=0&rspCodeAddr4=0&rspCodeDob=0&rspCustomerDec=&trnType=P&paymentMethod=CC&ref1=&ref2=&ref3=&ref4=&ref5= pre authorization (PA) ---------------------- Prior to processing a pre-authorization through the API, you must modify the transaction settings in your Beanstream merchant member area to allow for this transaction type. - Log in to the Beanstream online member area at www.beanstream.com/admin/sDefault.asp. - Navigate to administration - account admin - order settings in the left menu. Under the heading �Restrict Internet Transaction Processing Types,� select either of the last two options. The �Purchases or Pre-Authorization Only� option will allow you to process both types of transaction through your web interface. De-selecting the �Restrict Internet Transaction Processing Types� checkbox will allow you to process all types of transactions including returns, voids and pre-auth completions. capture (PAC) -> requires trnId ------------- refund (R) ------------- REQUEST -> merchant_id=123456789&requestType=BACKEND&trnType=R&username=user1234&password=pass1234&trnOrderNumber=1234&trnAmount=1.00&adjId=10002115 RESPONSE-> trnApproved=1&trnId=10002118&messageId=1&messageText=Approved&trnOrderNumber=1234R&authCode=TEST&errorType=N&errorFields=&responseType=T&trnAmount=1%2E00&trnDate=8%2F17%2F2009+1%3A44%3A56+PM&avsProcessed=0&avsId=0&avsResult=0&avsAddrMatch=0&avsPostalMatch=0&avsMessage=Address+Verification+not+performed+for+this+transaction%2E&cardType=VI&trnType=R&paymentMethod=CC&ref1=&ref2=&ref3=&ref4=&ref5= //notes //On receipt of the transaction response, the merchant must display order amount, transaction ID number, bank authorization code (authCode), currency, date and �messageText� to the customer on a confirmation page. */ //String agent = "Mozilla/4.0"; //String respText = ""; //Map nvp = null; /** debug **/ StringBuffer messageLogString = new StringBuffer(); messageLogString.append("requestType=BACKEND&"); messageLogString.append("merchant_id=").append(configuration.getIntegrationKeys().get("merchantid")).append("&"); messageLogString.append("trnType=").append(type).append("&"); messageLogString.append("orderNumber=").append(orderNumber).append("&"); messageLogString.append("trnCardOwner=").append(creditCardPayment.getCardOwner()).append("&"); messageLogString.append("trnCardNumber=").append(CreditCardUtils.maskCardNumber(creditCardPayment.getCreditCardNumber())).append("&"); messageLogString.append("trnExpMonth=").append(creditCardPayment.getExpirationMonth()).append("&"); messageLogString.append("trnExpYear=").append(creditCardPayment.getExpirationYear()).append("&"); messageLogString.append("trnCardCvd=").append(creditCardPayment.getCredidCardValidationNumber()).append("&"); messageLogString.append("trnAmount=").append(amnt).append("&"); messageLogString.append("ordName=").append(nm.toString()).append("&"); messageLogString.append("ordAddress1=").append(customer.getBilling().getAddress()).append("&"); messageLogString.append("ordCity=").append(customer.getBilling().getCity()).append("&"); messageLogString.append("ordProvince=").append(stateProvince).append("&"); messageLogString.append("ordPostalCode=").append(customer.getBilling().getPostalCode()).append("&"); messageLogString.append("ordCountry=").append(customer.getBilling().getCountry().getName()).append("&"); messageLogString.append("ordPhoneNumber=").append(customer.getBilling().getTelephone()).append("&"); messageLogString.append("ordEmailAddress=").append(customer.getEmailAddress()); /** debug **/ LOGGER.debug("REQUEST SENT TO BEANSTREAM -> " + messageLogString.toString()); URL postURL = new URL(server.toString()); conn = (HttpURLConnection) postURL.openConnection(); Transaction response = this.sendTransaction(orderNumber, store, messageString.toString(), transactionType, type, payment.getPaymentType(), amount, configuration, module); return response; } catch(Exception e) { if(e instanceof IntegrationException) throw (IntegrationException)e; throw new IntegrationException("Error while processing BeanStream transaction",e); } finally { if (conn != null) { try { conn.disconnect(); } catch (Exception ignore) {} } } } private Transaction parseResponse(TransactionType transactionType, PaymentType paymentType, Map<String,String> nvp, BigDecimal amount) throws Exception { Transaction transaction = new Transaction(); transaction.setAmount(amount); //transaction.setOrder(order); transaction.setTransactionDate(new Date()); transaction.setTransactionType(transactionType); transaction.setPaymentType(PaymentType.CREDITCARD); transaction.getTransactionDetails().put("TRANSACTIONID", (String)nvp.get("TRNID")); transaction.getTransactionDetails().put("TRNAPPROVED", (String)nvp.get("TRNAPPROVED")); transaction.getTransactionDetails().put("TRNORDERNUMBER", (String)nvp.get("TRNORDERNUMBER")); transaction.getTransactionDetails().put("MESSAGETEXT", (String)nvp.get("MESSAGETEXT")); if(nvp.get("INTERNALORDERID")!=null) { transaction.getTransactionDetails().put("INTERNALORDERID", (String)nvp.get("INTERNALORDERID")); } return transaction; } private Map formatUrlResponse(String payload) throws Exception { HashMap<String,String> nvp = new HashMap<String,String> (); StringTokenizer stTok = new StringTokenizer(payload, "&"); while (stTok.hasMoreTokens()) { StringTokenizer stInternalTokenizer = new StringTokenizer(stTok .nextToken(), "="); if (stInternalTokenizer.countTokens() == 2) { String key = URLDecoder.decode(stInternalTokenizer.nextToken(), "UTF-8"); String value = URLDecoder.decode(stInternalTokenizer .nextToken(), "UTF-8"); nvp.put(key.toUpperCase(), value); } } return nvp; } @Override public void validateModuleConfiguration( IntegrationConfiguration integrationConfiguration, MerchantStore store) throws IntegrationException { List<String> errorFields = null; Map<String,String> keys = integrationConfiguration.getIntegrationKeys(); //validate integrationKeys['merchantid'] if(keys==null || StringUtils.isBlank(keys.get("merchantid"))) { errorFields = new ArrayList<String>(); errorFields.add("merchantid"); } //validate integrationKeys['username'] if(keys==null || StringUtils.isBlank(keys.get("username"))) { if(errorFields==null) { errorFields = new ArrayList<String>(); } errorFields.add("username"); } //validate integrationKeys['password'] if(keys==null || StringUtils.isBlank(keys.get("password"))) { if(errorFields==null) { errorFields = new ArrayList<String>(); } errorFields.add("password"); } if(errorFields!=null) { IntegrationException ex = new IntegrationException(IntegrationException.ERROR_VALIDATION_SAVE); ex.setErrorFields(errorFields); throw ex; } } }