/*
jBilling - The Enterprise Open Source Billing System
Copyright (C) 2003-2011 Enterprise jBilling Software Ltd. and Emiliano Conde
This file is part of jbilling.
jbilling is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jbilling 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with jbilling. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sapienter.jbilling.server.payment.tasks;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Calendar;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.log4j.Logger;
import com.sapienter.jbilling.server.payment.PaymentAuthorizationBL;
import com.sapienter.jbilling.server.payment.PaymentDTOEx;
import com.sapienter.jbilling.server.payment.db.PaymentAuthorizationDTO;
import com.sapienter.jbilling.server.payment.db.PaymentResultDAS;
import com.sapienter.jbilling.server.pluggableTask.PaymentTask;
import com.sapienter.jbilling.server.pluggableTask.PaymentTaskWithTimeout;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.user.ContactBL;
import com.sapienter.jbilling.server.user.contact.db.ContactDTO;
import com.sapienter.jbilling.server.util.Constants;
public class PaymentsGatewayTask extends PaymentTaskWithTimeout implements
PaymentTask {
// mandatory parameters
public static final String PARAMETER_MERCHANT_ID = "merchant_id";
public static final String PARAMETER_PASSWORD = "password";
private static final String PARAMETER_HOST = "host"; // "www.paymentsgateway.net";
private static final String PARAMETER_PORT = "port"; // 6050
// optional parameters
public static final String PARAMETER_AVS = "submit_avs";
public static final String PARAMETER_TEST = "test";
private static final String PARAMETER_TEST_HOST = "test_host"; // "www.paymentsgateway.net";
private static final String PARAMETER_TEST_PORT = "test_port"; // 6050
private static final int CONNECTION_TIME_OUT = 10000; // in millisec
private static final int REPLY_TIME_OUT = 30000; // in millisec
// Credit Card Types
private static final int CC_TYPE_VISA = 2;
private static final int CC_TYPE_MASTER = 3;
private static final int CC_TYPE_AMEX = 4;
private static final int CC_TYPE_DISC = 6;
private static final int CC_TYPE_DINE = 7;
// unsupported though
private static final int CC_TYPE_JCB = 8;
/*
* jBilling defs. public static final Integer PAYMENT_METHOD_CHEQUE = new
* Integer(1); public static final Integer PAYMENT_METHOD_VISA = new
* Integer(2); public static final Integer PAYMENT_METHOD_MASTERCARD = new
* Integer(3); public static final Integer PAYMENT_METHOD_AMEX = new
* Integer(4); public static final Integer PAYMENT_METHOD_ACH = new
* Integer(5); public static final Integer PAYMENT_METHOD_DISCOVERY = new
* Integer(6); public static final Integer PAYMENT_METHOD_DINERS = new
* Integer(7); public static final Integer PAYMENT_METHOD_PAYPAL = new
* Integer(8);
*/
// Payment Method
private static final int PAYMENT_METHOD_CC = 1;
private static final int PAYMENT_METHOD_ACH = 2;
private static final int PAYMENT_METHOD_CHEQUE = 3;
// CC Transaction Types
private static final String CC_SALE = "10";
private static final String CC_AUTH = "11";
private static final String CC_CAPT = "12";
private static final String CC_CRED = "13"; // CC Refunds
// CC Transaction Types
private static final String EFT_SALE = "20";
private static final String EFT_AUTH = "21";
private static final String EFT_CAPT = "22";
private static final String EFT_CRED = "23"; // ACH Refund
private static final String EFT_VERIFY = "26"; // EFT Verify Only - for use
// with ATMVerify (R)
// Response Type
private static final String RESPONSE_CODE_APPROVED = "A"; // Approved
private static final String RESPONSE_CODE_DECLINED = "D"; // Declined
private static final String RESPONSE_CODE_ERROR = "E"; // Exception
private Logger log;
private String payloadData = "";
public PaymentsGatewayTask() {
log = Logger.getLogger(PaymentsGatewayTask.class);
}
public boolean process(PaymentDTOEx paymentInfo)
throws PluggableTaskException {
boolean retValue = false;
try {
int method = -1;
boolean preAuth = false;
if (paymentInfo.getIsPreauth() != null
&& paymentInfo.getIsPreauth().intValue() == 1) {
preAuth = true;
}
log.debug("Payment request Received ; Method : "
+ paymentInfo.getMethod());
if (paymentInfo.getCreditCard() != null) {
method = PAYMENT_METHOD_CC;
} else if (paymentInfo.getCheque() != null && paymentInfo.getAch() != null) {
method = PAYMENT_METHOD_CHEQUE;
} else if (paymentInfo.getAch() != null) {
method = PAYMENT_METHOD_ACH;
} else {
// hmmm problem
log.error("Can't process without a credit card, ach or cheque");
throw new PluggableTaskException(
"Credit card/ACH/Cheque not present in payment");
}
// if (paymentInfo.getIsRefund() == 1
// && (paymentInfo.getPayment() == null || paymentInfo
// .getPayment().getAuthorization() == null)) {
// log.error("Can't process refund without a payment with an"
// + " authorization record");
// throw new PluggableTaskException("Refund without previous "
// + "authorization");
// }
// prepare common data for sending to Gateway
validateParameters();
String data = getChargeData(paymentInfo, method, preAuth);
PaymentAuthorizationDTO response = processPGRequest(data);
paymentInfo.setAuthorization(response);
log.debug("Response code " + response.getCode1());
if (RESPONSE_CODE_APPROVED.equals(response.getCode1())) {
paymentInfo.setPaymentResult(new PaymentResultDAS()
.find(Constants.RESULT_OK));
log.debug("result is ok");
} else {
paymentInfo.setPaymentResult(new PaymentResultDAS()
.find(Constants.RESULT_FAIL));
log.debug("result is fail");
}
PaymentAuthorizationBL bl = new PaymentAuthorizationBL();
bl.create(response, paymentInfo.getId());
} catch (PluggableTaskException e) {
log.error("PluggableTaskException", e);
throw e;
} catch (Exception e) {
log.error("Exception", e);
throw new PluggableTaskException(e);
}
log.debug("process returning " + retValue);
return retValue;
}
private String getChargeData(PaymentDTOEx paymentInfo, int method,
boolean preAuth) throws PluggableTaskException {
String payloadData = new String("");
try {
payloadData += "pg_merchant_id="
+ ensureGetParameter(PARAMETER_MERCHANT_ID) + "\n";
payloadData += "pg_password="
+ ensureGetParameter(PARAMETER_PASSWORD) + "\n";
payloadData += "pg_total_amount="
+ (paymentInfo.getAmount().abs()).toString() + "\n";
payloadData += "pg_transaction_type="
+ getTransType(paymentInfo, method, preAuth) + "\n";
// common data
// pg_consumer_id-assigned by merchant, returned with response
// ecom_consumerorderid-assigned by merchant, returned with response
// ecom_walletid-assigned by merchant, returned with response
// pg_billto_postal_name_company-company name
Integer userId = paymentInfo.getUserId();
ContactBL contact = new ContactBL();
contact.set(userId);
ContactDTO entity = contact.getEntity();
payloadData += "Ecom_BillTo_Postal_Name_First="
+ entity.getFirstName() + "\n";
payloadData += "Ecom_BillTo_Postal_Name_Last="
+ entity.getLastName() + "\n";
if ("true".equals(getOptionalParameter(PARAMETER_AVS, "false"))) {
payloadData += "ecom_billto_postal_street_line1="
+ entity.getAddress1() + "\n";
payloadData += "ecom_billto_postal_street_line2="
+ entity.getAddress2() + "\n";
payloadData += "ecom_billto_postal_city=" + entity.getCity()
+ "\n";
payloadData += "ecom_billto_postal_stateprov="
+ entity.getStateProvince() + "\n";
payloadData += "ecom_billto_postal_postalcode="
+ entity.getPostalCode() + "\n";
payloadData += "ecom_billto_postal_countrycode="
+ entity.getCountryCode() + "\n";
payloadData += "ecom_billto_telecom_phone_number="
+ entity.getPhoneNumber() + "\n";
payloadData += "ecom_billto_online_email=" + entity.getEmail()
+ "\n";
}
// pg_billto_ssn-customer?s social security number
// pg_billto_dl_number-customer?s driver?s license number
// pg_billto_dl_state-customer?s driver?s license state of issue
// pg_billto_date_of_birth-customer?s date of birth (MM/DD/YYYY)
// pg_entered_by-name or ID of the person entering the data; appears
// in the Virtual Terminal transaction display window
// payloadData+="pg_customer_ip_address="+InetAddress.getLocalHost().getHostAddress()+"\n";
payloadData += "pg_customer_ip_address=1.1.11.1\n";
payloadData += "pg_software_name=jBilling\n";
payloadData += "pg_software_version=2.0.0\n";
// pg_avs_method-specifies which AVS checks to perform on the
// transaction (if any);
// makes some optional fields required. See Appendix C for more
// information on AVS.
if (method == PAYMENT_METHOD_CC) {
String ccType = getCCType(paymentInfo.getCreditCard().getType());
payloadData += "ecom_payment_card_type=" + ccType + "\n";
payloadData += "ecom_payment_card_name="
+ paymentInfo.getCreditCard().getName() + "\n";
payloadData += "ecom_payment_card_number="
+ paymentInfo.getCreditCard().getNumber() + "\n";
Calendar calendar = Calendar.getInstance();
calendar.setTime(paymentInfo.getCreditCard().getCcExpiry());
payloadData += "ecom_payment_card_expdate_month="
+ calendar.get(Calendar.MONTH) + "\n";
payloadData += "ecom_payment_card_expdate_year="
+ calendar.get(Calendar.YEAR) + "\n";
payloadData += "ecom_payment_card_verification="
+ paymentInfo.getCreditCard().getSecurityCode() + "\n";
} else if (method == PAYMENT_METHOD_ACH) {
/*
* "Ecom_Payment_Check_AccounT_Type=S",
* "Ecom_Payment_Check_Account= 14730",
* "Ecom_Payment_Check_TRN=021000021",
*/
String accType = "";
payloadData += "Ecom_Payment_Check_TRN="
+ paymentInfo.getAch().getAbaRouting() + "\n";
payloadData += "Ecom_Payment_Check_Account="
+ paymentInfo.getAch().getBankAccount() + "\n";
if (paymentInfo.getAch().getAccountType() == 1) {
accType += "C";
} else if (paymentInfo.getAch().getAccountType() == 2) {
accType += "S";
} else {
log.error("unknown Account Type : "
+ paymentInfo.getAch().getAccountType());
throw new PluggableTaskException("unknown Account Type");
}
payloadData += "Ecom_Payment_Check_AccounT_Type=" + accType
+ "\n";
} else if (method == PAYMENT_METHOD_CHEQUE) {
payloadData += "Ecom_Payment_Check_TRN="
+ paymentInfo.getAch().getAbaRouting() + "\n";
payloadData += "Ecom_Payment_Check_Account="
+ paymentInfo.getAch().getBankAccount() + "\n";
payloadData += "Ecom_Payment_Check_Account_Type=C\n";
payloadData += "ecom_payment_check_checkno=" +
paymentInfo.getCheque().getNumber() + "\n";
// payloadData += "PG_Entry_Class_Code=POS\n";
}
} catch (PluggableTaskException e) {
log.error("PluggableTaskException", e);
throw e;
} catch (Exception e) {
log.error("Exception", e);
throw new PluggableTaskException(e);
}
payloadData += "endofdata\n";
log.debug("charge data : " + payloadData);
return payloadData;
}
/*
* ecom_payment_card_type-credit card issuer from Table 5-Credit Card Issuer
* Types below ecom_payment_card_name-cardholder name as it appears on the
* card ecom_payment_card_number-card account number
* ecom_payment_card_expdate_month-numeric month of expiration (Jan = 1)
* ecom_payment_card_expdate_year-four-digit year of expiration
* ecom_payment_card_verification-CVV2/verification number
* pg_procurement_card-indicates procurement card transaction, requires
* pg_sales_tax and pg_customer_acct_code fields
* pg_customer_acct_code-accounting information for procurement card
* transactions pg_cc_swipe_data-magstripe data from track one or two
* pg_mail_or_phone_order-indicates mail order or phone order transaction
* (as opposed to an Internet-based transaction)
*/
private void validateParameters() throws PluggableTaskException {
ensureGetParameter(PARAMETER_MERCHANT_ID);
ensureGetParameter(PARAMETER_PASSWORD);
}
public void failure(Integer userId, Integer retry) {
}
/*
* Credit Card 10 SALE Customer is charged 11 AUTH ONLY Authorization only,
* CAPTURE transaction required 12 CAPTURE Completes AUTH ONLY transaction
* 13 CREDIT Customer is credited 14 VOID Cancels non-settled transactions
* 15 PRE-AUTH Customer charge approved from other source EFT 20 SALE
* Customer is charged 21 AUTH ONLY Authorization only, CAPTURE transaction
* required 22 CAPTURE Completes AUTH ONLY transaction 23 CREDIT Customer is
* credited 24 VOID Cancels non-settled transactions 25 FORCE Customer
* charged (no validation checks) 26 VERIFY ONLY Verification only, no
* customer charge
*/
public String getTransType(PaymentDTOEx paymentInfo, int method,
boolean preAuth) throws PluggableTaskException {
String transType = new String();
if (paymentInfo.getIsRefund() == 1 ||
paymentInfo.getAmount().compareTo(BigDecimal.ZERO) < 0) {
if (method == PAYMENT_METHOD_CC) {
transType += CC_CRED;
} else if (method == PAYMENT_METHOD_ACH) {
transType += EFT_CRED;
} else {
log.error("Can't process refund for this method: " + method);
throw new PluggableTaskException(
"Can't process refund for this method");
}
} else if (paymentInfo.getIsRefund() == 0) {
switch (method) {
case PAYMENT_METHOD_CC:
if (preAuth) {
transType += CC_AUTH;
} else {
transType += CC_SALE;
}
break;
case PAYMENT_METHOD_ACH:
if (preAuth) {
transType += EFT_AUTH;
} else {
transType += EFT_SALE;
}
break;
case PAYMENT_METHOD_CHEQUE:
transType += EFT_VERIFY;
break;
default:
log.error("Unknown payment method : " + method);
throw new PluggableTaskException(
"Unknown payment method : Neither Credit Card, Cheque nor ACH ");
}
} else {
log.error("Unknown transaction type : "
+ paymentInfo.getIsRefund());
throw new PluggableTaskException(
"Unknown transaction type : Neither Credit Card, Cheque nor ACH");
}
return transType;
}
public String getCCType(Integer type) {
log.debug("credit card type: " + type);
String ccType = "";
switch (type) {
case CC_TYPE_VISA:
ccType += "VISA";
break;
case CC_TYPE_MASTER:
ccType += "MAST";
break;
case CC_TYPE_AMEX:
ccType += "AMER";
break;
case CC_TYPE_DISC:
ccType += "DISC";
break;
case CC_TYPE_DINE:
ccType += "DINE";
break;
case CC_TYPE_JCB:
ccType += "JCB";
break;
default:
log.error("Unknown credit card type: " + type);
break;
// throw new PluggableTaskException("Cannot find credit type");
}
return ccType;
}
private PaymentAuthorizationDTO processPGRequest(String data)
throws PluggableTaskException {
PaymentAuthorizationDTO dbRow = new PaymentAuthorizationDTO();
String negRep = "";
String autOut = "";
try {
BufferedReader br = callPG(data);
String line = br.readLine();
// log.debug("Response line: "+br);
while (line != null) {
// check for end of message
if (line.equals("endofdata")) {
log.debug("ENDOFDATA");
break;
}
log.debug("Response line: " + line);
// parse and display name/value pair
int equalPos = line.indexOf('=');
String name = line.substring(0, equalPos);
String value = line.substring(equalPos + 1);
log.debug(name + "=" + value);
if (name.equals("pg_response_type")) {
dbRow.setCode1(value); // code if 1 it is ok
}
if (name.equals("pg_response_code")) {
dbRow.setCode2(value);
}
if (name.equals("pg_authorization_code")) {
dbRow.setApprovalCode(value);
}
if (name.equals("pg_response_description")) {
dbRow.setResponseMessage(value);
}
if (name.equals("pg_trace_number")) {
dbRow.setTransactionId(value);
}
// preAuth
if (name.equals("pg_preauth_result")) {
dbRow.setCode3(value);
}
// Verify
if(name.equals("pg_preauth_description")) {
autOut = value;
}
if(name.equals("pg_preauth_neg_report")) {
negRep = value;
}
// get next line
line = br.readLine();
}
br.close();
} catch (Exception e) {
log.error("Error processing payment", e);
}
dbRow.setProcessor("PaymentsGateway");
if (autOut != null && !"".equals(autOut.trim())) {
dbRow.setResponseMessage(dbRow.getResponseMessage() + " - " + autOut);
}
if (negRep != null && !"".equals(negRep.trim()) && !negRep.equals(autOut)) {
dbRow.setResponseMessage(dbRow.getResponseMessage() + " (" + negRep + ")");
}
return dbRow;
}
public boolean preAuth(PaymentDTOEx payment) throws PluggableTaskException {
log = Logger.getLogger(PaymentsGatewayTask.class);
log.error("Prcessing preAuth Reqquest");
int method = 1; // CC
boolean preAuth = true;
if (payment.getCreditCard() != null) {
method = PAYMENT_METHOD_CC;
} else if (payment.getCheque() != null && payment.getAch() != null) {
method = PAYMENT_METHOD_CHEQUE;
} else if (payment.getAch() != null) {
method = PAYMENT_METHOD_ACH;
} else {
// hmmm problem
log.error("Can't process without a credit card or ach");
throw new PluggableTaskException(
"Credit card/ACH not present in payment");
}
try {
validateParameters();
String data = getChargeData(payment, method, preAuth);
PaymentAuthorizationDTO response = processPGRequest(data);
PaymentAuthorizationDTO authDtoEx = new PaymentAuthorizationDTO(
response);
PaymentAuthorizationBL bl = new PaymentAuthorizationBL();
bl.create(authDtoEx, payment.getId());
payment.setAuthorization(authDtoEx);
return false;
} catch (Exception e) {
log.error("error trying to pre-authorize", e);
return true;
}
}
/*
* pay load for confirmPreAuth
*
* Pg_merchant_id N8 M pg_password A20 M pg_transaction_type L M
* pg_merchant_data_[1-9] A40 O pg_original_trace_number A36 M
* pg_original_authorization_code A80 C (AC)
*/
public boolean confirmPreAuth(PaymentAuthorizationDTO auth,
PaymentDTOEx paymentInfo) throws PluggableTaskException {
log.error("Processing confirmPreAuth Request");
boolean retValue = false;
try {
if (!RESPONSE_CODE_APPROVED.equals(auth.getCode1())) {
log.error("Cannot process failed preAuth");
return retValue;
}
payloadData += "pg_merchant_id="
+ ensureGetParameter(PARAMETER_MERCHANT_ID) + "\n";
payloadData += "pg_password="
+ ensureGetParameter(PARAMETER_PASSWORD) + "\n";
String transType = "";
if (paymentInfo.getCreditCard() != null) {
transType += CC_CAPT;
} else if (paymentInfo.getAch() != null) {
transType += EFT_CAPT;
} else {
// hmmm problem!!! this should not happen
log.error("Can't process without a credit card or ach");
throw new PluggableTaskException(
"Credit card/ACH not present in payment");
// return false;
}
payloadData += "pg_transaction_type=" + transType + "\n";
payloadData += "pg_original_trace_number="
+ auth.getTransactionId() + "\n";
payloadData += "pg_original_authorization_code="
+ auth.getApprovalCode() + "\n";
payloadData += "endofdata\n";
BufferedReader br = callPG(payloadData);
String line = br.readLine();
while (line != null) {
// check for end of message
if (line.equals("endofdata")) {
log.debug("ENDOFDATA");
break;
}
log.debug("Response line: " + line);
// parse and display name/value pair
int equalPos = line.indexOf('=');
String name = line.substring(0, equalPos);
String value = line.substring(equalPos + 1);
if (name.equals("pg_response_type")) {
if (RESPONSE_CODE_APPROVED.equals(value)) {
paymentInfo.setPaymentResult(new PaymentResultDAS()
.find(Constants.RESULT_OK));
log.debug("preAuth result is ok");
retValue = false;
} else {
paymentInfo.setPaymentResult(new PaymentResultDAS()
.find(Constants.RESULT_FAIL));
log.debug("preAuth result is failed");
retValue = true;
}
auth.setCode1(value);
}
if (name.equals("pg_response_code")) {
auth.setCode2(value);
}
if (name.equals("pg_authorization_code")) {
auth.setApprovalCode(value);
}
if (name.equals("pg_response_description")) {
auth.setResponseMessage(value);
}
if (name.equals("pg_trace_number")) {
auth.setTransactionId(value);
}
// get next line
line = br.readLine();
}
br.close();
} catch (Exception e) {
log.error("error trying to confirm pre-authorize", e);
throw new PluggableTaskException(e);
}
return retValue;
}
private BufferedReader callPG(String data) throws PluggableTaskException {
String host = null;
int port;
if ("true".equals(getOptionalParameter(PARAMETER_TEST, "false"))) {
host = super.ensureGetParameter(PARAMETER_TEST_HOST);
port = Integer.valueOf(
super.ensureGetParameter(PARAMETER_TEST_PORT)).intValue();
log.debug("Running task in test mode!");
} else {
host = super.ensureGetParameter(PARAMETER_HOST);
port = Integer.valueOf(super.ensureGetParameter(PARAMETER_PORT))
.intValue();
}
try {
// set up secure connection w/JSSE
java.security.Security
.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
SocketFactory factory = (SocketFactory) SSLSocketFactory
.getDefault();
SSLSocket s = (SSLSocket) factory.createSocket(host, port);
log.debug("connected to :" + host + "on " + port);
s.setEnabledCipherSuites(s.getSupportedCipherSuites());
log.debug("cipher=" + s.getSession().getCipherSuite());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
// write the content and be sure to flush
log.debug("Writing data to PG " + data);
dos.writeBytes(data);
dos.flush();
// read the response
BufferedReader br = new BufferedReader(new InputStreamReader(s
.getInputStream()));
return br;
} catch (Exception e) {
log.error("Error processing payment", e);
return null;
}
// return null;
}
}