/*
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 com.sapienter.jbilling.common.Constants;
import com.sapienter.jbilling.common.Util;
import com.sapienter.jbilling.server.payment.IExternalCreditCardStorage;
import com.sapienter.jbilling.server.payment.PaymentDTOEx;
import com.sapienter.jbilling.server.payment.db.PaymentAuthorizationDTO;
import com.sapienter.jbilling.server.payment.db.PaymentDAS;
import com.sapienter.jbilling.server.payment.db.PaymentDTO;
import com.sapienter.jbilling.server.payment.db.PaymentMethodDAS;
import com.sapienter.jbilling.server.pluggableTask.admin.PluggableTaskException;
import com.sapienter.jbilling.server.user.ContactBL;
import com.sapienter.jbilling.server.user.UserBL;
import com.sapienter.jbilling.server.user.contact.db.ContactDTO;
import com.sapienter.jbilling.server.user.db.CreditCardDTO;
import com.sapienter.jbilling.server.user.db.AchDTO;
import com.sapienter.jbilling.server.user.db.UserDTO;
import org.apache.log4j.Logger;
import java.math.BigDecimal;
import java.util.Date;
/**
* @author Brian Cowdery
* @since 20-10-2009
*/
public class PaymentWorldPayExternalTask extends PaymentWorldPayBaseTask implements IExternalCreditCardStorage {
private static final Logger LOG = Logger.getLogger(PaymentWorldPayExternalTask.class);
@Override
String getProcessorName() { return "WorldPay"; }
public boolean process(PaymentDTOEx payment) throws PluggableTaskException {
LOG.debug("Payment processing for " + getProcessorName() + " gateway");
if (payment.getPayoutId() != null) return true;
/* build a ReAuthorize request if the payment instrument has a gateway key to
be used, otherwise create a new Sale transaction using the raw CC data.
if the payment amount is negative or refund is set, do a Credit transaction.
*/
prepareExternalPayment(payment);
SvcType transaction = (BigDecimal.ZERO.compareTo(payment.getAmount()) > 0 || payment.getIsRefund() != 0
? SvcType.REFUND_CREDIT
: (payment.getCreditCard().useGatewayKey()
? SvcType.RE_AUTHORIZE
: SvcType.SALE));
// process
LOG.debug("creating " + transaction + " payment transaction");
Result result = doProcess(payment, transaction, null);
// update the stored external gateway key
if (Constants.RESULT_OK.equals(payment.getResultId()))
updateGatewayKey(payment);
return result.shouldCallOtherProcessors();
}
public void failure(Integer userId, Integer retry) {
// not supported
}
public boolean preAuth(PaymentDTOEx payment) throws PluggableTaskException {
LOG.debug("Pre-authorization processing for " + getProcessorName() + " gateway");
prepareExternalPayment(payment);
return doProcess(payment, SvcType.AUTHORIZE, null).shouldCallOtherProcessors();
}
public boolean confirmPreAuth(PaymentAuthorizationDTO auth, PaymentDTOEx payment)
throws PluggableTaskException {
LOG.debug("Confirming pre-authorization for " + getProcessorName() + " gateway");
if (!getProcessorName().equals(auth.getProcessor())) {
/* let the processor be called and fail, so the caller can do something
about it: probably re-call this payment task as a new "process()" run */
LOG.warn("The processor of the pre-auth is not " + getProcessorName() + ", is " + auth.getProcessor());
}
CreditCardDTO card = payment.getCreditCard();
if (card == null) {
throw new PluggableTaskException("Credit card is required, capturing payment: " + payment.getId());
}
if (!isApplicable(payment)) {
LOG.error("This payment can not be captured" + payment);
return true;
}
// process
prepareExternalPayment(payment);
Result result = doProcess(payment, SvcType.SETTLE, auth);
// update the stored external gateway key
if (Constants.RESULT_OK.equals(payment.getResultId()))
updateGatewayKey(payment);
return result.shouldCallOtherProcessors();
}
/**
* Prepares a given payment to be processed using an external storage gateway key instead of
* the raw credit card number. If the associated credit card has been obscured it will be
* replaced with the users stored credit card from the database, which contains all the relevant
* external storage data.
*
* New or un-obscured credit cards will be left as is.
*
* @param payment payment to prepare for processing from external storage
*/
public void prepareExternalPayment(PaymentDTOEx payment) {
if (payment.getCreditCard().useGatewayKey()) {
LOG.debug("credit card is obscured, retrieving from database to use external store.");
payment.setCreditCard(new UserBL(payment.getUserId()).getCreditCard());
} else {
LOG.debug("new credit card or previously un-obscured, using as is.");
}
}
/**
* Updates the gateway key of the credit card associated with this payment. RBS WorldPay
* implements the gateway key a per-transaction ORDER_ID that is returned as part of the
* payment response.
*
* @param payment successful payment containing the credit card to update.
* */
public void updateGatewayKey(PaymentDTOEx payment) {
PaymentAuthorizationDTO auth = payment.getAuthorization();
// update the gateway key with the returned RBS WorldPay ORDER_ID
CreditCardDTO card = payment.getCreditCard();
card.setGatewayKey(auth.getTransactionId());
// obscure new credit card numbers
if (!Constants.PAYMENT_METHOD_GATEWAY_KEY.equals(card.getCcType()))
card.obscureNumber();
}
/**
* {@inheritDoc}
*
* Creates a payment of zero dollars and returns the RBC WorldPay ORDER_ID as the gateway
* key to be stored for future transactions.
*/
public String storeCreditCard(ContactDTO contact, CreditCardDTO creditCard, AchDTO ach) {
UserDTO user;
if (contact != null) {
UserBL bl = new UserBL(contact.getUserId());
user = bl.getEntity();
creditCard = bl.getCreditCard();
} else if (creditCard != null && !creditCard.getBaseUsers().isEmpty()) {
user = creditCard.getBaseUsers().iterator().next();
} else {
LOG.error("Could not determine user id for external credit card storage");
return null;
}
// new contact that has not had a credit card created yet
if (creditCard == null) {
LOG.warn("No credit card to store externally.");
return null;
}
/* Note, don't use PaymentBL.findPaymentInstrument() as the given creditCard is still being
processed at the time that this event is being handled, and will not be found.
PaymentBL()#create() will cause a stack overflow as it will attempt to update the credit card,
emitting another NewCreditCardEvent which is then handled by this method and repeated.
*/
PaymentDTO paymentInfo = new PaymentDTO();
paymentInfo.setBaseUser(user);
paymentInfo.setCurrency(user.getCurrency());
paymentInfo.setAmount(BigDecimal.ZERO);
paymentInfo.setCreditCard(creditCard);
paymentInfo.setPaymentMethod(new PaymentMethodDAS().find(Util.getPaymentMethod(creditCard.getNumber())));
paymentInfo.setIsRefund(0);
paymentInfo.setIsPreauth(0);
paymentInfo.setDeleted(0);
paymentInfo.setAttempt(1);
paymentInfo.setPaymentDate(new Date());
paymentInfo.setCreateDatetime(new Date());
PaymentDTOEx payment = new PaymentDTOEx(new PaymentDAS().save(paymentInfo));
try {
doProcess(payment, SvcType.SALE, null);
} catch (PluggableTaskException e) {
LOG.error("Could not process external storage payment", e);
return null;
}
// if result is OK, return authorization transaction id as the gateway key
return Constants.RESULT_OK.equals(payment.getResultId())
? payment.getAuthorization().getTransactionId()
: null;
}
/**
*
*/
public String deleteCreditCard(ContactDTO contact, CreditCardDTO creditCard, AchDTO ach) {
//noop
return null;
}
@Override // implements abstract method
public NVPList buildRequest(PaymentDTOEx payment, SvcType transaction) throws PluggableTaskException {
NVPList request = new NVPList();
request.add(PARAMETER_MERCHANT_ID, getMerchantId());
request.add(PARAMETER_STORE_ID, getStoreId());
request.add(PARAMETER_TERMINAL_ID, getTerminalId());
request.add(PARAMETER_SELLER_ID, getSellerId());
request.add(PARAMETER_PASSWORD, getPassword());
request.add(WorldPayParams.General.AMOUNT, formatDollarAmount(payment.getAmount()));
request.add(WorldPayParams.General.SVC_TYPE, transaction.getCode());
CreditCardDTO card = payment.getCreditCard();
/* Sale transactions do not support the use of the ORDER_ID gateway key. After an initial sale
transaction RBS WorldPay will have a record of our transactions for reference - so all
other transaction types are safe for use with the stored gateway key.
*/
if (SvcType.SALE.equals(transaction)
&& Constants.PAYMENT_METHOD_GATEWAY_KEY.equals(Util.getPaymentMethod(card.getNumber()))) {
throw new PluggableTaskException("Cannot process a SALE transaction with an obscured credit card!");
}
if (card.useGatewayKey()) {
request.add(WorldPayParams.ReAuthorize.ORDER_ID, card.getGatewayKey());
} else {
ContactBL contact = new ContactBL();
contact.set(payment.getUserId());
request.add(WorldPayParams.General.STREET_ADDRESS, contact.getEntity().getAddress1());
request.add(WorldPayParams.General.CITY, contact.getEntity().getCity());
request.add(WorldPayParams.General.STATE, contact.getEntity().getStateProvince());
request.add(WorldPayParams.General.ZIP, contact.getEntity().getPostalCode());
request.add(WorldPayParams.General.FIRST_NAME, contact.getEntity().getFirstName());
request.add(WorldPayParams.General.LAST_NAME, contact.getEntity().getLastName());
request.add(WorldPayParams.General.COUNTRY, contact.getEntity().getCountryCode());
request.add(WorldPayParams.CreditCard.CARD_NUMBER, card.getNumber());
request.add(WorldPayParams.CreditCard.EXPIRATION_DATE, EXPIRATION_DATE_FORMAT.format(card.getCcExpiry()));
if (card.getSecurityCode() != null) {
request.add(WorldPayParams.CreditCard.CVV2, String.valueOf(payment.getCreditCard().getSecurityCode()));
}
}
return request;
}
}