VerkkomaksutFiBean.java 15.1 KB
/*
 * Copyright Codecrew Ry
 * 
 * All rights reserved.
 * 
 * This license applies to any software containing a notice placed by the 
 * copyright holder. Such software is herein referred to as the Software. 
 * This license covers modification, distribution and use of the Software. 
 * 
 * Any distribution and use in source and binary forms, with or without 
 * modification is not permitted without explicit written permission from the 
 * copyright owner. 
 * 
 * A non-exclusive royalty-free right is granted to the copyright owner of the 
 * Software to use, modify and distribute all modifications to the Software in 
 * future versions of the Software. 
 * 
 */
package fi.codecrew.moya.beans;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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.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.CredentialsProvider;
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.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
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.codecrew.moya.beanutil.DecimalXMLAdapter;
import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import fi.codecrew.moya.enums.apps.BillPermission;
import fi.codecrew.moya.facade.BillFacade;
import fi.codecrew.moya.facade.LanEventPrivatePropertyFacade;
import fi.codecrew.moya.model.Bill;
import fi.codecrew.moya.model.LanEventPrivateProperty;
import fi.codecrew.moya.model.LanEventPrivatePropertyKey;
import fi.codecrew.moya.util.SvmReturnType;
import fi.codecrew.moya.util.VerkkomaksutReturnEntry;
import fi.codecrew.moya.utilities.PasswordFunctions;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
import fi.codecrew.moya.verkkomaksutfi.PaymentEntry;

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

	private static final Logger logger = LoggerFactory.getLogger(VerkkomaksutFiBean.class);
	private static final String CHECKSUM_SEP = "|";
	@EJB
	private EventBean eventbean;
	@EJB
	private PermissionBeanLocal permbean;

	@EJB
	private LoggingBeanLocal logbean;
	@EJB
	private BillFacade billFacade;
	@EJB
	private BillPBean billpbean;

	@Override
	public boolean isSvmEnabled()
	{
		if (!permbean.hasPermission(BillPermission.CREATE_VERKKOMAKSU))
		{
			return false;
		}
		LanEventPrivateProperty expire = eventbean.getPrivateProperty(LanEventPrivatePropertyKey.VERKKOMAKSU_KEY_EXPIRE);
		String merchantid = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID);
		String merchantPassword = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD);
		return !((expire != null && expire.getDateValue() != 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)
	{

		if (authcode == null || authcode.isEmpty() || orderNumber == null || orderNumber.isEmpty() || timestamp == null || timestamp.isEmpty())
		{
			return false;
		}
		String merchantPassword = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD);

		boolean ret = false;

		String calculatedHash = PasswordFunctions.calculateMd5(CHECKSUM_SEP,
				orderNumber,
				timestamp,
				paid,
				method,
				merchantPassword);
		logger.info("calculated checksum for svv message: {}, comparing to {}", calculatedHash, authcode);
		if (authcode.toUpperCase().equals(calculatedHash)) {
			Bill bill = billFacade.find(Integer.parseInt(orderNumber));
			if (bill != null)
			{
				// If bill is unpaid, mark it paid...
				if (SvmReturnType.PENDING.equals(type) || paid.equals("0000000000")) {
					logbean.sendMessage(MoyaEventType.BANKING_MESSAGE, 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))) {
					billpbean.markPaid(bill, Calendar.getInstance(), false);

					logbean.sendMessage(MoyaEventType.BANKING_MESSAGE, permbean.getCurrentUser(), "Validated order number ", orderNumber, " bill ", bill == null ? "null" : bill.toString(), " with authcode: ", authcode);
					ret = true;
				} else {
					logbean.sendMessage(MoyaEventType.BANKING_ERROR, permbean.getCurrentUser(), "Bill already marked paid or other error. ", orderNumber, " bill ", bill.toString(), " with authcode: ", authcode);
					ret = true;
				}
			} else {
				logbean.sendMessage(MoyaEventType.BANKING_ERROR, permbean.getCurrentUser(), "Mac validated, but unable to find bill for order number ", orderNumber);
			}
		} else {
			logbean.sendMessage(MoyaEventType.BANKING_ERROR, permbean.getCurrentUser(), "Unable to validate order number: ", orderNumber, " calculated checksum: ", calculatedHash, " authcode ", authcode, " paid ", paid, " method ", method);

		}

		return ret;
	}

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

		LanEventPrivateProperty expire = eventbean.getPrivateProperty(LanEventPrivatePropertyKey.VERKKOMAKSU_KEY_EXPIRE);
		String merchantid = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID);
		String merchantPassword = eventbean.getPrivatePropertyString(LanEventPrivatePropertyKey.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("/MoyaWeb/svm/").toString();

		PaymentEntry message = new PaymentEntry(returnUrl);
		message.setOrderNumber(bill.getId().toString());
		message.setReferenceNumber(bill.getReferenceNumber());
		message.setPrice(bill.totalPrice());
		// message.setDescription();

		VerkkomaksutReturnEntry ret = sendMessage(message, merchantid, merchantPassword);
		if (ret != null)
			if (ret.isError()) {
				logbean.sendMessage(MoyaEventType.BANKING_ERROR, permbean.getCurrentUser(), "User trieed to create new token for bill: ", bill.toString(), " but received error: ", ret.getErrorCode(), " message ", ret.getErrorMessage());
			} else {
				logbean.sendMessage(MoyaEventType.BANKING_MESSAGE, 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 AUTH_SCOPE = "payment.paytrail.com";
	private static final String REMOTE_URL =
			"https://payment.paytrail.com/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;
		CloseableHttpClient client = null;
		try {
			// DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpClientBuilder clientBuilder = HttpClientBuilder.create();

			CredentialsProvider credsProvider = new BasicCredentialsProvider();
			credsProvider.setCredentials(
					new AuthScope(AUTH_SCOPE, AuthScope.ANY_PORT, AuthScope.ANY_REALM),
					new UsernamePasswordCredentials(merchantId, merchantPassword));

			clientBuilder.setDefaultCredentialsProvider(credsProvider);

			client = clientBuilder.build();

			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());

		} catch (Throwable t) {
			logger.warn("Got exception while creating payment to suomenverkkomaksut", t);
		} finally {
			try {
				if (client != null)
					client.close();
			} catch (IOException e) {
				logger.warn("Error closing client for verkkomaksutfi", e);
			}

		}
		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/MoyaWeb/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;
		for (int i = 0; i < rootElements.getLength(); ++i) {
			Node node = rootElements.item(i);
			logger.info("Got root: {} {}, name {}", i, node, node.getNodeName());
		}

		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);
	}

	@EJB
	private LanEventPrivatePropertyFacade eventPrivatePropertyFacade;

	@Override
	public String getMerchantId() {
		String ret = null;
		if (isSvmEnabled() && permbean.hasPermission(BillPermission.WRITE_ALL)) {
			LanEventPrivateProperty e = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID);
			if (e != null && e.getTextvalue() != null && !e.getTextvalue().isEmpty())
				ret = e.getTextvalue();
		}
		return ret;
	}

	@Override
	@RolesAllowed(BillPermission.S_WRITE_ALL)
	public Map<Integer, String> getAuthcodeForBills(List<Bill> bills) {
		Map<Integer, String> ret = new HashMap<>();
		if (isSvmEnabled() && permbean.hasPermission(BillPermission.WRITE_ALL)) {
			String merchantId = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID).getTextvalue();
			String merchantPassword = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD).getTextvalue();
			for (Bill b : bills) {
				ret.put(b.getId(), PasswordFunctions.calculateMd5("&", merchantPassword, merchantId, b.getId().toString()).toUpperCase());
			}
		}
		return ret;
	}

	@Override
	@RolesAllowed(BillPermission.S_WRITE_ALL)
	public String getAuthcodeForBill(Bill selectedBill) {
		if (selectedBill == null) {
			return null;
		}
		final Bill b = billFacade.reload(selectedBill);
		if (b == null) {
			return null;
		}
		String merchantId = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_ID).getTextvalue();
		String merchantPassword = eventPrivatePropertyFacade.getPropertyForEvent(LanEventPrivatePropertyKey.VERKKOMAKSU_MERCHANT_PASSWORD).getTextvalue();
		return PasswordFunctions.calculateMd5("&", merchantPassword, merchantId, b.getId().toString()).toUpperCase();

	}
}