VerkkomaksutFiBean.java 11.7 KB
package fi.insomnia.bortal.beans;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.codec.binary.Hex;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.auth.params.AuthPNames;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import fi.insomnia.bortal.beanutil.DecimalXMLAdapter;
import fi.insomnia.bortal.clientutils.BortalLocalContextHolder;
import fi.insomnia.bortal.enums.apps.BillPermission;
import fi.insomnia.bortal.model.Bill;
import fi.insomnia.bortal.model.LanEventProperty;
import fi.insomnia.bortal.model.LanEventPropertyKey;
import fi.insomnia.bortal.util.SvmReturnType;
import fi.insomnia.bortal.util.VerkkomaksutReturnEntry;
import fi.insomnia.bortal.verkkomaksutfi.PaymentEntry;

/**
 * Session Bean implementation class VerkkomaksutFiBean
 */
@Stateless
@LocalBean
@DeclareRoles({ BillPermission.S_CREATE_VERKKOMAKSU })
public class VerkkomaksutFiBean implements VerkkomaksutFiBeanLocal {

	private static final Logger logger = LoggerFactory.getLogger(VerkkomaksutFiBean.class);
	private static final char CHECKSUM_SEP = '|';
	@EJB
	private EventBean lanbean;
	@EJB
	private BillBean billBean;
	@EJB
	private LoggingBeanLocal logbean;
	@EJB
	private PermissionBeanLocal permbean;

	@Override
	public boolean isSvmEnabled()
	{
		LanEventProperty expire = lanbean.getProperty(LanEventPropertyKey.VERKKOMAKSU_KEY_EXPIRE);
		String merchantid = lanbean.getPropertyString(LanEventPropertyKey.VERKKOMAKSU_MERCHANT_ID);
		String merchantPassword = lanbean.getPropertyString(LanEventPropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD);
		return !((expire != null && new Date().after(expire.getDateValue()))
				|| merchantid == null || merchantid.isEmpty()
				|| merchantPassword == null || merchantPassword.isEmpty());
	}

	@Override
	public boolean validateReturn(SvmReturnType type, String orderNumber, String timestamp, String paid, String method, String authcode)
	{
		String merchantPassword = lanbean.getPropertyString(LanEventPropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD);

		StringBuilder sumSource = new StringBuilder().append(orderNumber).append(CHECKSUM_SEP)
				.append(timestamp).append(CHECKSUM_SEP)
				.append(paid).append(CHECKSUM_SEP)
				.append(method).append(CHECKSUM_SEP)
				.append(merchantPassword);
		boolean ret = false;
		try {
			MessageDigest algo = MessageDigest.getInstance("MD5");

			final byte[] resultByte = algo.digest(sumSource.toString().getBytes());
			final String calculatedHash = new String(Hex.encodeHex(resultByte));
			logger.info("calculated checksum for svv message: {}, comparing to {}", calculatedHash, authcode);
			if (authcode.toUpperCase().equals(calculatedHash.toUpperCase())) {
				Bill bill = billBean.findById(Integer.parseInt(orderNumber));
				if (bill != null)
				{
					// If bill is unpaid, mark it paid...
					if (SvmReturnType.PENDING.equals(type) || paid.equals("0000000000")) {
						logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Received pending message ", orderNumber, " bill ", bill == null ? "null" : bill.toString(), " with authcode: ", authcode);
					} else if (bill.getAccountEvent() == null
							&& bill.getPaidDate() == null
							&& (SvmReturnType.NOTIFICATION.equals(type) || SvmReturnType.SUCCESS.equals(type))) {
						billBean.markPaid(bill, Calendar.getInstance());
						logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Validated order number ", orderNumber, " bill ", bill == null ? "null" : bill.toString(), " with authcode: ", authcode);
						ret = true;
					} else {
						logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Bill already marked paid or other error. ", orderNumber, " bill ", bill == null ? "null" : bill.toString(), " with authcode: ", authcode);
						ret = true;
					}
				} else {
					logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Unable to find bill for order number ", orderNumber);
				}
			} else {
				logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "Unable to validate order number: ", orderNumber, " calculated checksum: ", calculatedHash, " authcode ", authcode, " paid ", paid, " method ", method);

			}
		} catch (NoSuchAlgorithmException e) {
			logger.warn("THIS SHOULD NEVER HAPPEN! (md5 hashfunction should always exist)", e);
		}
		return ret;
	}

	@Override
	@RolesAllowed(BillPermission.S_CREATE_VERKKOMAKSU)
	public VerkkomaksutReturnEntry getSvmToken(Bill bill)
	{

		LanEventProperty expire = lanbean.getProperty(LanEventPropertyKey.VERKKOMAKSU_KEY_EXPIRE);
		String merchantid = lanbean.getPropertyString(LanEventPropertyKey.VERKKOMAKSU_MERCHANT_ID);
		String merchantPassword = lanbean.getPropertyString(LanEventPropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD);
		if ((expire != null && new Date().after(expire.getDateValue()))
				|| merchantid == null || merchantid.isEmpty()
				|| merchantPassword == null || merchantPassword.isEmpty())
		{
			return null;
		}
		String returnUrl = new StringBuilder("http://").append(BortalLocalContextHolder.getHostname())
				.append("/LanBortalWeb/svm/").toString();

		PaymentEntry message = new PaymentEntry(returnUrl);
		message.setOrderNumber(bill.getId().toString());

		message.setPrice(bill.totalPrice());
		// message.setDescription();

		VerkkomaksutReturnEntry ret = sendMessage(message, merchantid, merchantPassword);
		if (ret.isError()) {
			logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "User trieed to create new token for bill: ", bill.toString(), " but received error: ", ret.getErrorCode(), " message ", ret.getErrorMessage());
		} else {
			logbean.logMessage(SecurityLogType.verkkomaksu, permbean.getCurrentUser(), "User crated new token for bill: ", bill.toString(), " Got: ", ret.getOrderNumber(), " token ", ret.getToken());

		}

		return ret;
	}

	private static String outputPayment(PaymentEntry payment) throws JAXBException {

		JAXBContext context = JAXBContext.newInstance(PaymentEntry.class);
		Marshaller m = context.createMarshaller();
		m.setAdapter(new DecimalXMLAdapter());
		m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
		StringWriter st = new StringWriter();
		m.marshal(payment, st);

		String ret = st.toString();
		logger.info("Created xml to verkkomaksut {}", ret);
		return ret;

	}

	private static final String REMOTE_URL =
			"https://payment.verkkomaksut.fi/api-payment/create";

	// private static final String url = "http://iudex.fi/api-payment/create";

	private static VerkkomaksutReturnEntry sendMessage(PaymentEntry paymentMsg, String merchantId, String merchantPassword)
	{

		VerkkomaksutReturnEntry ret = null;
		try {
			// DefaultHttpClient httpClient = new DefaultHttpClient();
			DefaultHttpClient client = new DefaultHttpClient();
			client.getCredentialsProvider().setCredentials(
					// new AuthScope("payment.verkkomaksut.fi", 443),
					AuthScope.ANY,
					new UsernamePasswordCredentials(merchantId, merchantPassword));
			client.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, Arrays.asList(AuthPolicy.BASIC));

			HttpPost postRequest = new HttpPost(REMOTE_URL);

			postRequest.setHeader("Content-Type", "application/xml");
			postRequest.setHeader("X-Verkkomaksut-Api-Version", "1");
			postRequest.setEntity(new StringEntity(outputPayment(paymentMsg)));

			HttpResponse response = client.execute(postRequest);

			logger.info("Got statuscode from suomenverkkomaksut.fi: {} ", response.getStatusLine().getStatusCode());
			ret = parseMessageReturn(response.getEntity().getContent());

			client.getConnectionManager().shutdown();
		} catch (Throwable t)
		{
			logger.warn("Got exception while creating payment to suomenverkkomaksut", t);
		}
		return ret;

	}

	public static void main(String[] foo) throws SAXException, IOException, ParserConfigurationException, JAXBException
	{
		// String msg =
		// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<payment><orderNumber>12345678</orderNumber><token>SECRET TOKEN STRING GENRATED BY API</token><url>https://payment.verkkomaksut.fi/payment/load/token/SECRET TOKEN STRING GENRATED BY API</url></payment>";
		// System.out.println(msg);
		// StringInputStream istream = new StringInputStream(msg);
		// VerkkomaksutReturnEntry ret = parseMessageReturn(istream);
		// logger.info("URL: {}", ret.getUrl());
		// logger.info("ordernr: {}", ret.getOrderNumber());
		// logger.info("token: {}", ret.getToken());

		PaymentEntry message = new PaymentEntry("http://localhost:8080/LanBortalWeb/svm/");
		message.setOrderNumber("123123");

		message.setPrice(BigDecimal.valueOf(123.45));
		System.out.println(outputPayment(message));
	}

	private static VerkkomaksutReturnEntry parseMessageReturn(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException {
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document parsed = dBuilder.parse(inputStream);
		parsed.normalize();
		NodeList rootElements = parsed.getChildNodes();
		VerkkomaksutReturnEntry ret = null;
		if (rootElements.getLength() != 1) {
			logError("Unknown number of entries for root {}", parsed, rootElements.getLength());
		} else {
			ret = new VerkkomaksutReturnEntry();

			Node rootElem = rootElements.item(0);
			NodeList rootChildren = rootElem.getChildNodes();

			if (rootElem.getNodeName().equals("payment")) {
				for (int i = 0; i < rootChildren.getLength(); ++i)
				{
					Node child = rootChildren.item(i);
					String nodeName = child.getNodeName();
					String nodeValue = child.getTextContent();
					logger.debug("Setting node {} value {}", nodeName, nodeValue);
					if (nodeName.equals("orderNumber")) {
						ret.setOrderNumber(nodeValue);
					} else if (nodeName.equals("token")) {
						ret.setToken(nodeValue);
					} else if (nodeName.equals("url")) {
						ret.setUrl(nodeValue);
					} else {
						logger.warn("unknown payment xml param from suomenverkkomaksut: {} with value {}", nodeName, child.getNodeValue());
					}
				}
			} else if (rootElem.getNodeName().equals("error")) {
				for (int i = 0; i < rootChildren.getLength(); ++i)
				{
					Node child = rootChildren.item(i);
					String nodeName = child.getNodeName();
					String nodeValue = child.getTextContent();

					if (nodeName.equals("errorCode")) {
						ret.setErrorCode(nodeValue);
					} else if (nodeName.equals("errorMessage")) {
						ret.setErrorMessage(nodeValue);
					} else {
						logger.warn("unknown error xml param from suomenverkkomaksut: {} with value {}", nodeName, child.getNodeValue());
					}
				}
				logError("Received error xml from suomenverkkomaksut", parsed);
			} else {
				logError("Received unknown XML from suomenverkkomaksut", parsed);
				ret = null;
			}
		}
		return ret;
	}

	private static void logError(String msg, Document doc, Object... params) {

		logger.warn(msg, params);
	}
}