/** * <p>Title: AIM Java Version 1.4.1_02-b06</p> * <p>Description: Advanced Integration Method</p> * <p>Copyright: Copyright (c) 2013</p> * <p>Company: BluePay</p> * @author Walter Wojcik * @version 1.0 */ /** * Based on sample code and snipptes provided by: * Patrick Phelan, phelan@choicelogic.com * Roedy Green, Canadian Mind Products */ // Modifications by Adrian Romero & Mikel Irurita and further modified for blue pay by walter wojcik package com.openbravo.pos.payment; import com.openbravo.pos.forms.AppLocal; import com.openbravo.pos.forms.AppProperties; import com.openbravo.pos.util.StringUtils; import java.io.*; import java.net.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; /** * * @author JG uniCenta */ public class PaymentGatewayBluePay20POST implements PaymentGateway { private static String ENDPOINTADDRESS; private static final String APPROVED = "1"; private static final String PAYMENT_ACCOUNT = null; private String BP_AccountID; private String BP_SecretKey; private String BP_Tamper_Proof_Seal; private String BP_Master_ID; private String BP_Trans_Type; private boolean BP_TestMode; private String BP_Name1; /** Creates a new instance of PaymentGatewayBluePay20POST * @param props */ public PaymentGatewayBluePay20POST(AppProperties props) { // Grab some configuration variables BP_AccountID = props.getProperty("payment.BluePay20POST.accountID"); this.BP_SecretKey = props.getProperty("payment.BluePay20POST.secretKey"); BP_TestMode = Boolean.valueOf(props.getProperty("payment.testmode")).booleanValue(); ENDPOINTADDRESS = props.getProperty( "payment.BluePay20POST.URL" ); } /** * */ public PaymentGatewayBluePay20POST() { } /** * * @param payinfo */ @Override public void execute(PaymentInfoMagcard payinfo) { StringUtils.getCardNumber(); // JG 16 May 12 use StringBuilder in place of StringBuilder StringBuilder sb = new StringBuilder(); try { sb.append("ACCOUNT_ID="); sb.append(URLEncoder.encode(BP_AccountID, "UTF-8")); sb.append("&AMOUNT="); NumberFormat formatter = new DecimalFormat("######.00"); String amount = formatter.format(Math.abs(payinfo.getTotal())); sb.append(URLEncoder.encode(amount.replace(',', '.'), "UTF-8")); //PAYMENT if (payinfo.getTotal() >= 0.0) { BP_Master_ID = null; BP_Trans_Type = "SALE"; } //REFUND else { BP_Trans_Type = "REFUND"; /** * Detect BluePay Transaction IDs which will start with "1001" * This is not ideal but until the POS Devs get back to me or I * find a better way to detect this, it should be good enough */ BP_Master_ID = ( payinfo.getTransactionID().startsWith("1001") ? payinfo.getTransactionID() : null ) ; } if (payinfo.getTrack1(true) == null) { sb.append("&PAYMENT_ACCOUNT="); sb.append(URLEncoder.encode(payinfo.getCardNumber(), "UTF-8")); sb.append("&CARD_CVV2="); String tmp = payinfo.getExpirationDate(); sb.append(URLEncoder.encode(tmp, "UTF-8")); String[] cc_name = payinfo.getHolderName().split(" "); sb.append("&NAME1="); if (cc_name.length > 0) { sb.append(URLEncoder.encode(cc_name[0], "UTF-8")); } sb.append("&NAME2="); if (cc_name.length > 1) { sb.append(URLEncoder.encode(cc_name[1], "UTF-8")); } } else { // Example Track1 // %B4111111111111111^PADILLA VISDOMINE/LUIS^0905123000000000000002212322222?5 String swipe = URLEncoder.encode( payinfo.getTrack1(true), "UTF-8" ) + URLEncoder.encode( payinfo.getTrack2(true), "UTF-8" ); swipe = swipe.replace( "%0A", "" ); swipe = swipe.replace( "%5E", "^" ); if( BP_Trans_Type.equals( "REFUND" ) && BP_Master_ID != null ) { // Do nothing, SWIPE data is not need if there is a master ID when refunding. } else { sb.append("&SWIPE="); sb.append( swipe ); } } //sb.append("&x_method=CC"); sb.append("&MODE="); sb.append( BP_TestMode ? "TEST" : "LIVE" ); sb.append("&TRANS_TYPE="); sb.append(BP_Trans_Type); sb.append("&MASTER_ID="); sb.append( BP_Master_ID != null ? BP_Master_ID : "" ); sb.append( "&NAME1="); sb.append( URLEncoder.encode(payinfo.getHolderName().split(" ")[0], "UTF-8") ); // Start Tamper Proof Seal MessageDigest md5; try { md5 = MessageDigest.getInstance("MD5"); BP_Tamper_Proof_Seal = BP_SecretKey + BP_AccountID + BP_Trans_Type + amount + ( BP_Master_ID != null ? BP_Master_ID : "" ) + URLEncoder.encode(payinfo.getHolderName().split(" ")[0], "UTF-8") + (PAYMENT_ACCOUNT != null ? PAYMENT_ACCOUNT : ""); byte[] hash = md5.digest( BP_Tamper_Proof_Seal.getBytes("UTF-8") ); StringBuilder sbhash = new StringBuilder(2*hash.length); for(byte b : hash){ sbhash.append(String.format("%02x", b&0xff)); } String md5hex = sbhash.toString(); sb.append( "&TAMPER_PROOF_SEAL="); sb.append( URLEncoder.encode( md5hex, "UTF-8") ); } catch (NoSuchAlgorithmException ex) { Logger.getLogger(PaymentGatewayBluePay20POST.class.getName()).log(Level.SEVERE, null, ex); } // End Tamper Proof Seal // open secure connection URL url = new URL(ENDPOINTADDRESS); HttpsURLConnection connection = (HttpsURLConnection)url.openConnection(); connection.setDoOutput(true); connection.setUseCaches(false); // not necessarily required but fixes a bug with some servers // JG May 12 added 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 returned; /** * Throws IO Exception when the server returns 400. The server will * return this anytime the request is not formated perfectly * This needs to be caught and handled since the server will still * respond with useful information even after returning 400 */ try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { returned = in.readLine(); } // Custom parser to handle GET/POST style response BluePay20POSTParser results = new BluePay20POSTParser( returned ); /** * Server will return: * STATUS=1 when transaction is accepted * STATUS=2 When the transaction * STATUS=E Then there is an error with the request formating or data sent */ if( results.getStatus().equals("1") ) { payinfo.paymentOK( results.getAuth_Code(), results.getTrans_ID(), results.getMessage() ); } else if( results.getStatus().equals("2") ) { if( results.getMessage().equals( "DUPLICATE" )) { payinfo.paymentError( "DUPLICATE of Trans # " + results.Trans_ID + "", amount); } else payinfo.paymentError(results.getMessage(), amount); } else { /** * When there is an issue with the request the server will * return a 400 header and STAUTS=E with a short MESSAGE=Somthing * The 400 header makes BufferedReader Throw an IO Exception and * until I figure out how to catch it and save there stream there * is nothing to do here. */ } // JG 16 May 12 use multicatch } catch (UnsupportedEncodingException | MalformedURLException eUE) { payinfo.paymentError(AppLocal.getIntString("message.paymentexceptionservice"), eUE.getMessage()); } catch(IOException e) // Throw but buffered reader when the server returns a 400 header { payinfo.paymentError(AppLocal.getIntString("message.paymenterror"), e.getMessage()); } } private class BluePay20POSTParser { private String Trans_ID; private String Status; private String AVS; private String CVV2; private String Auth_Code; private String Message; private String Rebid; private String Trans_Type; public BluePay20POSTParser( String result ) { String[] pairs = result.split( "&" ); for( String pair : pairs ) { String[] parts = pair.split( "=" ); switch( parts[0] ) { case "TRANS_ID": this.Trans_ID = parts[1]; break; case "STATUS": this.Status = parts[1]; break; case "AVS": this.AVS = parts[1]; break; case "CVV2": this.CVV2 = parts[1]; break; case "AUTH_CODE": this.Auth_Code = parts[1]; break; case "MESSAGE": this.Message = parts[1]; break; case "Rebid": this.Rebid = parts[1]; break; case "TRANS_TYPE": this.Trans_Type = parts[1]; break; default: break; } } } public String getTrans_ID() { return this.Trans_ID; } public String getStatus() { return this.Status; } public String getAVS() { return this.AVS; } public String getCVV2() { return this.CVV2; } public String getAuth_Code() { return this.Auth_Code; } public String getMessage() { return this.Message; } public String getRebid() { return this.Rebid; } public String getTrans_Type() { return this.Trans_Type; } } }