package com.salesmanager.core.business.services.payments;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.salesmanager.core.business.constants.Constants;
import com.salesmanager.core.business.exception.ServiceException;
import com.salesmanager.core.business.services.order.OrderService;
import com.salesmanager.core.business.services.reference.loader.ConfigurationModulesLoader;
import com.salesmanager.core.business.services.system.MerchantConfigurationService;
import com.salesmanager.core.business.services.system.ModuleConfigurationService;
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.order.OrderTotal;
import com.salesmanager.core.model.order.OrderTotalType;
import com.salesmanager.core.model.order.orderstatus.OrderStatus;
import com.salesmanager.core.model.order.orderstatus.OrderStatusHistory;
import com.salesmanager.core.model.payments.CreditCardPayment;
import com.salesmanager.core.model.payments.CreditCardType;
import com.salesmanager.core.model.payments.Payment;
import com.salesmanager.core.model.payments.PaymentMethod;
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.MerchantConfiguration;
import com.salesmanager.core.modules.integration.IntegrationException;
import com.salesmanager.core.modules.integration.payment.model.PaymentModule;
import com.salesmanager.core.modules.utils.Encryption;
@Service("paymentService")
public class PaymentServiceImpl implements PaymentService {
private final static String PAYMENT_MODULES = "PAYMENT";
@Inject
private MerchantConfigurationService merchantConfigurationService;
@Inject
private ModuleConfigurationService moduleConfigurationService;
@Inject
private TransactionService transactionService;
@Inject
private OrderService orderService;
@Inject
@Resource(name="paymentModules")
private Map<String,PaymentModule> paymentModules;
@Inject
private Encryption encryption;
@Override
public List<IntegrationModule> getPaymentMethods(MerchantStore store) throws ServiceException {
List<IntegrationModule> modules = moduleConfigurationService.getIntegrationModules(PAYMENT_MODULES);
List<IntegrationModule> returnModules = new ArrayList<IntegrationModule>();
for(IntegrationModule module : modules) {
if(module.getRegionsSet().contains(store.getCountry().getIsoCode())
|| module.getRegionsSet().contains("*")) {
returnModules.add(module);
}
}
return returnModules;
}
@Override
public List<PaymentMethod> getAcceptedPaymentMethods(MerchantStore store) throws ServiceException {
Map<String,IntegrationConfiguration> modules = this.getPaymentModulesConfigured(store);
List<PaymentMethod> returnModules = new ArrayList<PaymentMethod>();
for(String module : modules.keySet()) {
IntegrationConfiguration config = modules.get(module);
if(config.isActive()) {
IntegrationModule md = this.getPaymentMethodByCode(store, config.getModuleCode());
if(md==null) {
continue;
}
PaymentMethod paymentMethod = new PaymentMethod();
paymentMethod.setDefaultSelected(config.isDefaultSelected());
paymentMethod.setPaymentMethodCode(config.getModuleCode());
paymentMethod.setModule(md);
paymentMethod.setInformations(config);
PaymentType type = PaymentType.fromString(md.getType());
/**
if(md.getType().equalsIgnoreCase(PaymentType.CREDITCARD.name())) {
type = PaymentType.CREDITCARD;
} else if(md.getType().equalsIgnoreCase(PaymentType.FREE.name())) {
type = PaymentType.FREE;
} else if(md.getType().equalsIgnoreCase(PaymentType.MONEYORDER.name())) {
type = PaymentType.MONEYORDER;
} else if(md.getType().equalsIgnoreCase(PaymentType.PAYPAL.name())) {
type = PaymentType.PAYPAL;
} else if(md.getType().equalsIgnoreCase(PaymentType.STRIPE.name())) {
type = PaymentType.STRIPE;
}**/
paymentMethod.setPaymentType(type);
returnModules.add(paymentMethod);
}
}
return returnModules;
}
@Override
public IntegrationModule getPaymentMethodByType(MerchantStore store, String type) throws ServiceException {
List<IntegrationModule> modules = getPaymentMethods(store);
for(IntegrationModule module : modules) {
if(module.getModule().equals(type)) {
return module;
}
}
return null;
}
@Override
public IntegrationModule getPaymentMethodByCode(MerchantStore store,
String code) throws ServiceException {
List<IntegrationModule> modules = getPaymentMethods(store);
for(IntegrationModule module : modules) {
if(module.getCode().equals(code)) {
return module;
}
}
return null;
}
@Override
public IntegrationConfiguration getPaymentConfiguration(String moduleCode, MerchantStore store) throws ServiceException {
Map<String,IntegrationConfiguration> configuredModules = getPaymentModulesConfigured(store);
if(configuredModules!=null) {
for(String key : configuredModules.keySet()) {
if(key.equals(moduleCode)) {
return configuredModules.get(key);
}
}
}
return null;
}
@Override
public Map<String,IntegrationConfiguration> getPaymentModulesConfigured(MerchantStore store) throws ServiceException {
try {
Map<String,IntegrationConfiguration> modules = new HashMap<String,IntegrationConfiguration>();
MerchantConfiguration merchantConfiguration = merchantConfigurationService.getMerchantConfiguration(PAYMENT_MODULES, store);
if(merchantConfiguration!=null) {
if(!StringUtils.isBlank(merchantConfiguration.getValue())) {
String decrypted = encryption.decrypt(merchantConfiguration.getValue());
modules = ConfigurationModulesLoader.loadIntegrationConfigurations(decrypted);
}
}
return modules;
} catch (Exception e) {
throw new ServiceException(e);
}
}
@Override
public void savePaymentModuleConfiguration(IntegrationConfiguration configuration, MerchantStore store) throws ServiceException {
//validate entries
try {
String moduleCode = configuration.getModuleCode();
PaymentModule module = (PaymentModule)paymentModules.get(moduleCode);
if(module==null) {
throw new ServiceException("Payment module " + moduleCode + " does not exist");
}
module.validateModuleConfiguration(configuration, store);
} catch (IntegrationException ie) {
throw ie;
}
try {
Map<String,IntegrationConfiguration> modules = new HashMap<String,IntegrationConfiguration>();
MerchantConfiguration merchantConfiguration = merchantConfigurationService.getMerchantConfiguration(PAYMENT_MODULES, store);
if(merchantConfiguration!=null) {
if(!StringUtils.isBlank(merchantConfiguration.getValue())) {
String decrypted = encryption.decrypt(merchantConfiguration.getValue());
modules = ConfigurationModulesLoader.loadIntegrationConfigurations(decrypted);
}
} else {
merchantConfiguration = new MerchantConfiguration();
merchantConfiguration.setMerchantStore(store);
merchantConfiguration.setKey(PAYMENT_MODULES);
}
modules.put(configuration.getModuleCode(), configuration);
String configs = ConfigurationModulesLoader.toJSONString(modules);
String encrypted = encryption.encrypt(configs);
merchantConfiguration.setValue(encrypted);
merchantConfigurationService.saveOrUpdate(merchantConfiguration);
} catch (Exception e) {
throw new ServiceException(e);
}
}
@Override
public void removePaymentModuleConfiguration(String moduleCode, MerchantStore store) throws ServiceException {
try {
Map<String,IntegrationConfiguration> modules = new HashMap<String,IntegrationConfiguration>();
MerchantConfiguration merchantConfiguration = merchantConfigurationService.getMerchantConfiguration(PAYMENT_MODULES, store);
if(merchantConfiguration!=null) {
if(!StringUtils.isBlank(merchantConfiguration.getValue())) {
String decrypted = encryption.decrypt(merchantConfiguration.getValue());
modules = ConfigurationModulesLoader.loadIntegrationConfigurations(decrypted);
}
modules.remove(moduleCode);
String configs = ConfigurationModulesLoader.toJSONString(modules);
String encrypted = encryption.encrypt(configs);
merchantConfiguration.setValue(encrypted);
merchantConfigurationService.saveOrUpdate(merchantConfiguration);
}
MerchantConfiguration configuration = merchantConfigurationService.getMerchantConfiguration(moduleCode, store);
if(configuration!=null) {//custom module
merchantConfigurationService.delete(configuration);
}
} catch (Exception e) {
throw new ServiceException(e);
}
}
@Override
public Transaction processPayment(Customer customer,
MerchantStore store, Payment payment, List<ShoppingCartItem> items, Order order)
throws ServiceException {
Validate.notNull(customer);
Validate.notNull(store);
Validate.notNull(payment);
Validate.notNull(order);
Validate.notNull(order.getTotal());
payment.setCurrency(store.getCurrency());
BigDecimal amount = order.getTotal();
//must have a shipping module configured
Map<String, IntegrationConfiguration> modules = this.getPaymentModulesConfigured(store);
if(modules==null){
throw new ServiceException("No payment module configured");
}
IntegrationConfiguration configuration = modules.get(payment.getModuleName());
if(configuration==null) {
throw new ServiceException("Payment module " + payment.getModuleName() + " is not configured");
}
if(!configuration.isActive()) {
throw new ServiceException("Payment module " + payment.getModuleName() + " is not active");
}
String sTransactionType = configuration.getIntegrationKeys().get("transaction");
if(sTransactionType==null) {
sTransactionType = TransactionType.AUTHORIZECAPTURE.name();
}
if(sTransactionType.equals(TransactionType.AUTHORIZE.name())) {
payment.setTransactionType(TransactionType.AUTHORIZE);
} else {
payment.setTransactionType(TransactionType.AUTHORIZECAPTURE);
}
PaymentModule module = this.paymentModules.get(payment.getModuleName());
if(module==null) {
throw new ServiceException("Payment module " + payment.getModuleName() + " does not exist");
}
if(payment instanceof CreditCardPayment) {
CreditCardPayment creditCardPayment = (CreditCardPayment)payment;
validateCreditCard(creditCardPayment.getCreditCardNumber(),creditCardPayment.getCreditCard(),creditCardPayment.getExpirationMonth(),creditCardPayment.getExpirationYear());
}
IntegrationModule integrationModule = getPaymentMethodByCode(store,payment.getModuleName());
TransactionType transactionType = TransactionType.valueOf(sTransactionType);
if(transactionType==null) {
transactionType = payment.getTransactionType();
if(transactionType.equals(TransactionType.CAPTURE.name())) {
throw new ServiceException("This method does not allow to process capture transaction. Use processCapturePayment");
}
}
Transaction transaction = null;
if(transactionType == TransactionType.AUTHORIZE) {
transaction = module.authorize(store, customer, items, amount, payment, configuration, integrationModule);
} else if(transactionType == TransactionType.AUTHORIZECAPTURE) {
transaction = module.authorizeAndCapture(store, customer, items, amount, payment, configuration, integrationModule);
} else if(transactionType == TransactionType.INIT) {
transaction = module.initTransaction(store, customer, amount, payment, configuration, integrationModule);
}
if(transactionType != TransactionType.INIT) {
transactionService.create(transaction);
}
if(transactionType == TransactionType.AUTHORIZECAPTURE) {
order.setStatus(OrderStatus.ORDERED);
if(payment.getPaymentType().name()!=PaymentType.MONEYORDER.name()) {
order.setStatus(OrderStatus.PROCESSED);
}
}
return transaction;
}
@Override
public PaymentModule getPaymentModule(String paymentModuleCode) throws ServiceException {
return paymentModules.get(paymentModuleCode);
}
@Override
public Transaction processCapturePayment(Order order, Customer customer,
MerchantStore store)
throws ServiceException {
Validate.notNull(customer);
Validate.notNull(store);
Validate.notNull(order);
//must have a shipping module configured
Map<String, IntegrationConfiguration> modules = this.getPaymentModulesConfigured(store);
if(modules==null){
throw new ServiceException("No payment module configured");
}
IntegrationConfiguration configuration = modules.get(order.getPaymentModuleCode());
if(configuration==null) {
throw new ServiceException("Payment module " + order.getPaymentModuleCode() + " is not configured");
}
if(!configuration.isActive()) {
throw new ServiceException("Payment module " + order.getPaymentModuleCode() + " is not active");
}
PaymentModule module = this.paymentModules.get(order.getPaymentModuleCode());
if(module==null) {
throw new ServiceException("Payment module " + order.getPaymentModuleCode() + " does not exist");
}
IntegrationModule integrationModule = getPaymentMethodByCode(store,order.getPaymentModuleCode());
//TransactionType transactionType = payment.getTransactionType();
//get the previous transaction
Transaction trx = transactionService.getCapturableTransaction(order);
if(trx==null) {
throw new ServiceException("No capturable transaction for order id " + order.getId());
}
Transaction transaction = module.capture(store, customer, order, trx, configuration, integrationModule);
transaction.setOrder(order);
transactionService.create(transaction);
OrderStatusHistory orderHistory = new OrderStatusHistory();
orderHistory.setOrder(order);
orderHistory.setStatus(OrderStatus.PROCESSED);
orderHistory.setDateAdded(new Date());
orderService.addOrderStatusHistory(order, orderHistory);
order.setStatus(OrderStatus.PROCESSED);
orderService.saveOrUpdate(order);
return transaction;
}
@Override
public Transaction processRefund(Order order, Customer customer,
MerchantStore store, BigDecimal amount)
throws ServiceException {
Validate.notNull(customer);
Validate.notNull(store);
Validate.notNull(amount);
Validate.notNull(order);
Validate.notNull(order.getOrderTotal());
BigDecimal orderTotal = order.getTotal();
if(amount.doubleValue()>orderTotal.doubleValue()) {
throw new ServiceException("Invalid amount, the refunded amount is greater than the total allowed");
}
String module = order.getPaymentModuleCode();
Map<String, IntegrationConfiguration> modules = this.getPaymentModulesConfigured(store);
if(modules==null){
throw new ServiceException("No payment module configured");
}
IntegrationConfiguration configuration = modules.get(module);
if(configuration==null) {
throw new ServiceException("Payment module " + module + " is not configured");
}
PaymentModule paymentModule = this.paymentModules.get(module);
if(paymentModule==null) {
throw new ServiceException("Payment module " + paymentModule + " does not exist");
}
boolean partial = false;
if(amount.doubleValue()!=order.getTotal().doubleValue()) {
partial = true;
}
IntegrationModule integrationModule = getPaymentMethodByCode(store,module);
//get the associated transaction
Transaction refundable = transactionService.getRefundableTransaction(order);
if(refundable==null) {
throw new ServiceException("No refundable transaction for this order");
}
Transaction transaction = paymentModule.refund(partial, store, refundable, order, amount, configuration, integrationModule);
transaction.setOrder(order);
transactionService.create(transaction);
OrderTotal refund = new OrderTotal();
refund.setModule(Constants.OT_REFUND_MODULE_CODE);
refund.setText(Constants.OT_REFUND_MODULE_CODE);
refund.setTitle(Constants.OT_REFUND_MODULE_CODE);
refund.setOrderTotalCode(Constants.OT_REFUND_MODULE_CODE);
refund.setOrderTotalType(OrderTotalType.REFUND);
refund.setValue(amount);
refund.setSortOrder(100);
refund.setOrder(order);
order.getOrderTotal().add(refund);
//update order total
orderTotal = orderTotal.subtract(amount);
//update ordertotal refund
Set<OrderTotal> totals = order.getOrderTotal();
for(OrderTotal total : totals) {
if(total.getModule().equals(Constants.OT_TOTAL_MODULE_CODE)) {
total.setValue(orderTotal);
}
}
order.setTotal(orderTotal);
order.setStatus(OrderStatus.REFUNDED);
OrderStatusHistory orderHistory = new OrderStatusHistory();
orderHistory.setOrder(order);
orderHistory.setStatus(OrderStatus.REFUNDED);
orderHistory.setDateAdded(new Date());
order.getOrderHistory().add(orderHistory);
orderService.saveOrUpdate(order);
return transaction;
}
@Override
public void validateCreditCard(String number, CreditCardType creditCard, String month, String date)
throws ServiceException {
try {
Integer.parseInt(month);
Integer.parseInt(date);
} catch (NumberFormatException nfe) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid date format","messages.error.creditcard.dateformat");
throw ex;
}
if (StringUtils.isBlank(number)) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
Matcher m = Pattern.compile("[^\\d\\s.-]").matcher(number);
if (m.find()) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
Matcher matcher = Pattern.compile("[\\s.-]").matcher(number);
number = matcher.replaceAll("");
validateCreditCardDate(Integer.parseInt(month), Integer.parseInt(date));
validateCreditCardNumber(number, creditCard);
}
private void validateCreditCardDate(int m, int y) throws ServiceException {
java.util.Calendar cal = new java.util.GregorianCalendar();
int monthNow = cal.get(java.util.Calendar.MONTH) + 1;
int yearNow = cal.get(java.util.Calendar.YEAR);
if (yearNow > y) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid date format","messages.error.creditcard.dateformat");
throw ex;
}
// OK, change implementation
if (yearNow == y && monthNow > m) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid date format","messages.error.creditcard.dateformat");
throw ex;
}
}
@Deprecated
/**
* Use commons validator CreditCardValidator
* @param number
* @param creditCard
* @throws ServiceException
*/
private void validateCreditCardNumber(String number, CreditCardType creditCard)
throws ServiceException {
//TODO implement
if(CreditCardType.MASTERCARD.equals(creditCard.name())) {
if (number.length() != 16
|| Integer.parseInt(number.substring(0, 2)) < 51
|| Integer.parseInt(number.substring(0, 2)) > 55) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
if(CreditCardType.VISA.equals(creditCard.name())) {
if ((number.length() != 13 && number.length() != 16)
|| Integer.parseInt(number.substring(0, 1)) != 4) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
if(CreditCardType.AMEX.equals(creditCard.name())) {
if (number.length() != 15
|| (Integer.parseInt(number.substring(0, 2)) != 34 && Integer
.parseInt(number.substring(0, 2)) != 37)) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
if(CreditCardType.DINERS.equals(creditCard.name())) {
if (number.length() != 14
|| ((Integer.parseInt(number.substring(0, 2)) != 36 && Integer
.parseInt(number.substring(0, 2)) != 38)
&& Integer.parseInt(number.substring(0, 3)) < 300 || Integer
.parseInt(number.substring(0, 3)) > 305)) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
if(CreditCardType.DISCOVERY.equals(creditCard.name())) {
if (number.length() != 16
|| Integer.parseInt(number.substring(0, 5)) != 6011) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
luhnValidate(number);
}
// The Luhn algorithm is basically a CRC type
// system for checking the validity of an entry.
// All major credit cards use numbers that will
// pass the Luhn check. Also, all of them are based
// on MOD 10.
@Deprecated
private void luhnValidate(String numberString)
throws ServiceException {
char[] charArray = numberString.toCharArray();
int[] number = new int[charArray.length];
int total = 0;
for (int i = 0; i < charArray.length; i++) {
number[i] = Character.getNumericValue(charArray[i]);
}
for (int i = number.length - 2; i > -1; i -= 2) {
number[i] *= 2;
if (number[i] > 9)
number[i] -= 9;
}
for (int i = 0; i < number.length; i++)
total += number[i];
if (total % 10 != 0) {
ServiceException ex = new ServiceException(ServiceException.EXCEPTION_VALIDATION,"Invalid card number","messages.error.creditcard.number");
throw ex;
}
}
}