CheckoutFiBean.java 12.1 KB
package fi.insomnia.bortal.beans;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.org.apache.xerces.internal.parsers.DOMParser;

import fi.insomnia.bortal.checkoutfi.CheckoutFiParam;
import fi.insomnia.bortal.clientutils.BortalLocalContextHolder;
import fi.insomnia.bortal.enums.apps.BillPermission;
import fi.insomnia.bortal.facade.BillFacade;
import fi.insomnia.bortal.model.Bill;
import fi.insomnia.bortal.model.LanEventPrivateProperty;
import fi.insomnia.bortal.model.LanEventPrivatePropertyKey;
import fi.insomnia.bortal.util.CheckoutBank;
import fi.insomnia.bortal.util.CheckoutReturnType;
import fi.insomnia.bortal.utilities.PasswordFunctions;

/**
 * Session Bean implementation class CheckoutFiBean
 */
@Stateless
@LocalBean
public class CheckoutFiBean implements CheckoutFiBeanLocal {

	private static final String STAMP_SPLITCHAR = "a";
	private static final String REMOTE_URL = "https://payment.checkout.fi";

	/**
	 * Default constructor.
	 */
	public CheckoutFiBean() {
		// TODO Auto-generated constructor stub
	}

	@EJB
	private EventBean eventbean;
	@EJB
	private PermissionBeanLocal permbean;
	@EJB
	private LoggingBeanLocal logbean;
	@EJB
	private BillFacade billfacade;
	@EJB
	private VerkkomaksuRunner vmrunner;

	private static final Logger logger = LoggerFactory.getLogger(CheckoutFiBean.class);
	private static final BigDecimal TO_CENTS = BigDecimal.valueOf(100);

	@Override
	public boolean isPaymentEnabled()
	{
		LanEventPrivateProperty expire = eventbean.getPrivateProperty(LanEventPrivatePropertyKey.CHECKOUT_FI_KEY_EXPIRE);
		String merchantid = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.CHECKOUT_FI_MERCHANT_ID);
		String merchantPassword = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.CHECKOUT_FI_MERCHANT_PASSWORD);
		return !((expire != null && new Date().after(expire.getDateValue()))
				|| merchantid == null || merchantid.isEmpty()
				|| merchantPassword == null || merchantPassword.isEmpty());
	}

	public static void main(String[] asd)
	{
		System.out.println(Calendar.getInstance().getTimeInMillis());
		System.out.println(Calendar.getInstance().getTimeInMillis() / 1000);
	}

	private static final String DATEFORMAT = "yyyyMMdd";
	private static final Set<String> DISCARD_BANKS = new HashSet<String>(Arrays.asList("ape", "tilisiirto", "neopay"));

	@Override
	@RolesAllowed(BillPermission.S_CREATE_VERKKOMAKSU)
	public List<CheckoutBank> getToken(Bill bill)
	{
		LanEventPrivateProperty expire = eventbean.getPrivateProperty(LanEventPrivatePropertyKey.CHECKOUT_FI_KEY_EXPIRE);
		String merchantid = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.CHECKOUT_FI_MERCHANT_ID);
		String merchantPassword = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.CHECKOUT_FI_MERCHANT_PASSWORD);
		if ((expire != null && new Date().after(expire.getDateValue()))
				|| merchantid == null || merchantid.isEmpty()
				|| merchantPassword == null || merchantPassword.isEmpty())
		{
			return null;
		}
		String priceInCents = Integer.valueOf(bill.totalPrice().multiply(TO_CENTS).intValue()).toString();
		Map<CheckoutFiParam, String> postParams = new HashMap<CheckoutFiParam, String>();

		String returnUrl = new StringBuilder("http://").append(BortalLocalContextHolder.getHostname()).append("/LanBortalWeb/checkout/").toString();
		StringBuilder stamp = new StringBuilder();
		stamp.append(bill.getId());
		stamp.append(STAMP_SPLITCHAR);
		stamp.append(Calendar.getInstance().getTimeInMillis() / 1000);

		logger.info("Created stamp {}", stamp.toString());

		postParams.put(CheckoutFiParam.STAMP, stamp.toString());
		postParams.put(CheckoutFiParam.AMOUNT, priceInCents);
		postParams.put(CheckoutFiParam.REFERENCE, bill.getReferenceNumber().toString());
		postParams.put(CheckoutFiParam.MERCHANT, merchantid);
		postParams.put(CheckoutFiParam.RETURN, returnUrl + "return.jsf");
		postParams.put(CheckoutFiParam.CANCEL, returnUrl + "cancel.jsf");
		postParams.put(CheckoutFiParam.REJECT, returnUrl + "reject.jsf");
		postParams.put(CheckoutFiParam.DELAYED, returnUrl + "delayed.jsf");
		postParams.put(CheckoutFiParam.DELIVERY_DATE, new SimpleDateFormat(DATEFORMAT).format(new Date()));

		List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
		StringBuilder mdString = new StringBuilder();

		for (CheckoutFiParam v : CheckoutFiParam.values())
		{
			String value = null;
			if (postParams.containsKey(v)) {
				value = postParams.get(v);
			} else {
				value = v.getDefaultValue();
			}
			if (value != null) {
				mdString.append(value);
				nameValuePairs.add(new BasicNameValuePair(v.name(), value));
			}
			mdString.append("+");

		}
		mdString.append(merchantPassword);

		try {

			final String calculatedHash = PasswordFunctions.calculateMd5(mdString.toString());
			logger.info("Calculated checksum {} from {}", mdString.toString(), calculatedHash);

			nameValuePairs.add(new BasicNameValuePair("MAC", calculatedHash));

			HttpPost postRequest = new HttpPost(REMOTE_URL);
			postRequest.setEntity(new UrlEncodedFormEntity(nameValuePairs));
			DefaultHttpClient client = new DefaultHttpClient();
			HttpResponse response = client.execute(postRequest);

			StringWriter writer = new StringWriter();
			IOUtils.copy(response.getEntity().getContent(), writer, "UTF8");
			String xmlReturn = writer.toString();
			logger.info("Got response from checkout.fi msg {}, {} ", response.getStatusLine().getStatusCode(), xmlReturn);

			return parseTokenXml(xmlReturn);

		} catch (UnsupportedEncodingException e) {
			logger.warn("Error sending checkout.fi request", e);
		} catch (ClientProtocolException e) {
			logger.warn("Error sending query to checkout.fi", e);
		} catch (IOException e) {
			logger.warn("Error sending query to checkout.fi", e);
		}

		return null;
	}

	// @Override
	// public List<CheckoutBank> testXml()
	// {
	// List<CheckoutBank> ret = null;
	// try {
	// BufferedReader reader = new BufferedReader(new
	// FileReader("/Users/tuomari/Documents/git/bortal/code/LanBortalBeans/ejbModule/fi/insomnia/bortal/beans/checkoutTestfile.xml"));
	// StringBuilder sb = new StringBuilder();
	//
	// String line = null;
	// while ((line = reader.readLine()) != null) {
	// sb.append(line);
	// sb.append("\n");
	// }
	//
	// reader.close();
	// String str = sb.toString();
	// ret = parseTokenXml(str);
	//
	// } catch (Throwable t)
	// {
	// logger.warn("error testing", t);
	// }
	// // logger.warn("str: {}", str);
	// return ret;
	// }

	private static List<CheckoutBank> parseTokenXml(String xmlReturn) {
		try {

			DOMParser parser = new DOMParser();
			parser.parse(new InputSource(new StringReader(xmlReturn)));
			Document doc = parser.getDocument();
			Element root = doc.getDocumentElement();
			root.normalize();
			// NodeList rootChildren = root.getChildNodes();
			// for (int i = 0; i < rootChildren.getLength(); ++i)
			// {
			// logger.info("Rootchild {}", rootChildren.item(i).getNodeName());
			// }

			Node payments = root.getElementsByTagName("banks").item(0);

			NodeList paymentElements = payments.getChildNodes();
			List<CheckoutBank> retbanks = new ArrayList<CheckoutBank>();
			for (int i = 0; i < paymentElements.getLength(); ++i)
			{
				Node bank = paymentElements.item(i);
				if (bank.getNodeType() == 1)
				{
					CheckoutBank newBank = new CheckoutBank(bank);
					if (!DISCARD_BANKS.contains(newBank.getKey()))
					{
						retbanks.add(newBank);
					}
				}

			}
			return retbanks;
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public boolean validateReturn(CheckoutReturnType returnType, String version, String stamp, String reference, String payment, String status, String algorithm, String mac) {
		if (returnType == null || mac == null || mac.isEmpty() || stamp == null || stamp.isEmpty())
		{
			return false;
		}
		String merchantPass = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.CHECKOUT_FI_MERCHANT_PASSWORD);

		boolean ret = false;
		String[] splittedStamp = stamp.split(STAMP_SPLITCHAR);
		if (splittedStamp.length != 2)
		{
			logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Unable to split stamp ", stamp, " with splitchar ", STAMP_SPLITCHAR);
			return false;
		}

		// MD5(turva-avain&VERSION&STAMP&REFERENCE&PAYMENT&STATUS&ALGORITHM)
		String calculatedMac = PasswordFunctions.calculateMd5("&", merchantPass, version, stamp, reference, payment, status, algorithm);
		if (calculatedMac.equals(mac.toUpperCase())) {
			Bill bill = billfacade.find(Integer.parseInt(splittedStamp[0]));
			if (bill != null)
			{
				switch (returnType)
				{
				case CANCEL:
					logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "received cancel for stamp ", stamp);
					// Return true when checksum was correct
					ret = true;
					break;
				case DELAYED:
					logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "received delayed for stamp ", stamp);
					// Return true when checksum was correct
					ret = true;
					break;
				case REJECT:
					logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "received reject for stamp ", stamp);
					// Return true when checksum was correct
					ret = true;
					break;
				case RETURN:
					int statusInt = Integer.parseInt(status);
					// Verkkokauppaohjelmisto voi hyväksyä maksun suoritetuksi
					// kun maksun tila on 2,5,6,7,8,9 tai 10.
					switch (statusInt) {
					case 2:
					case 5:
					case 6:
					case 7:
					case 8:
					case 9:
					case 10:
						if (bill.getAccountEvent() == null
								&& bill.getPaidDate() == null)
						{
							vmrunner.markPaid(bill, Calendar.getInstance());
							logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Marking bill paid. Received bill status ", statusInt, " for bill ", bill, " stamp ", stamp, " payment: ", payment, " reference ", reference);
							ret = true;
						} else {
							logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Bill already marked paid: ", bill, " status ", status, " stamp ", stamp, " payment ", payment);
						}
						break;
					default:
						logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Not marking bill paid: Return status ", status, " for bill ", bill, " stamp ", stamp, " payment ", payment);

						break;
					}
					break;
				default:
					logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Valid mac, but Invalid return type: ", returnType, " for stamp ", stamp, " payment ", payment, " status ", status);
					throw new EJBException("Unknown return type!");
				}
			}
			else {
				logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Validated mac, but bill not found for id: ", splittedStamp[0], " stamp ", stamp, " mac: ", mac);
			}
		} else {

			logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Unable to validate order reference: ", reference, " calculated checksum: ", calculatedMac, " version ", version, " stamp ", stamp, " status ", status, " mac ", mac);

		}

		return ret;
	}
}