· 5 years ago · Jan 17, 2020, 04:34 PM
1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5package rs.plusplusnt.midas.primitives.utils;
6
7import com.google.gson.*;
8import org.apache.commons.codec.binary.Base64;
9import org.apache.commons.codec.binary.Hex;
10import org.slf4j.Logger;
11import org.slf4j.LoggerFactory;
12import org.springframework.web.util.UriComponentsBuilder;
13import rs.plusplusnt.midas.primitives.Debit;
14import rs.plusplusnt.midas.primitives.DebitCustomParams;
15
16import javax.crypto.*;
17import javax.crypto.spec.SecretKeySpec;
18import javax.net.ssl.*;
19import java.io.*;
20import java.net.MalformedURLException;
21import java.net.URL;
22import java.nio.charset.StandardCharsets;
23import java.security.*;
24import java.security.cert.CertificateException;
25import java.security.spec.X509EncodedKeySpec;
26import java.text.*;
27import java.util.*;
28import java.util.concurrent.ConcurrentHashMap;
29
30
31public class PaymentUtil {
32 private static final Logger log = LoggerFactory.getLogger(PaymentUtil.class);
33
34 public static class SSLKey {
35 File trustStoreFile;
36 String trustStorePasswd;
37 File clientStoreFile;
38 String clientStorePasswd;
39
40 public SSLKey(File trustStoreFile, String trustStorePasswd, File clientStoreFile, String clientStorePasswd) {
41 this.trustStoreFile = trustStoreFile;
42 this.trustStorePasswd = trustStorePasswd;
43 this.clientStoreFile = clientStoreFile;
44 this.clientStorePasswd = clientStorePasswd;
45 }
46
47 @Override
48 public int hashCode() {
49 int hash = 7;
50 hash = 79 * hash + Objects.hashCode(this.trustStoreFile);
51 hash = 79 * hash + Objects.hashCode(this.trustStorePasswd);
52 hash = 79 * hash + Objects.hashCode(this.clientStoreFile);
53 hash = 79 * hash + Objects.hashCode(this.clientStorePasswd);
54 return hash;
55 }
56
57 @Override
58 public boolean equals(Object obj) {
59 if (this == obj) {
60 return true;
61 }
62 if (obj == null) {
63 return false;
64 }
65 if (getClass() != obj.getClass()) {
66 return false;
67 }
68
69 final SSLKey other = (SSLKey) obj;
70 if (!Objects.equals(this.trustStorePasswd, other.trustStorePasswd)) {
71 return false;
72 }
73 if (!Objects.equals(this.clientStorePasswd, other.clientStorePasswd)) {
74 return false;
75 }
76 if (!Objects.equals(this.trustStoreFile, other.trustStoreFile)) {
77 return false;
78 }
79 if (!Objects.equals(this.clientStoreFile, other.clientStoreFile)) {
80 return false;
81 }
82 return true;
83 }
84 }
85
86 private static final String TIMEZONE = "UTC";
87
88 private static final Map<SSLKey, javax.net.ssl.SSLSocketFactory> ssfCache = new ConcurrentHashMap<>();
89
90 public static String getTime() {
91 final TimeZone utcTimeZone = TimeZone.getTimeZone(TIMEZONE);
92 final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
93 dateFormat.setTimeZone(utcTimeZone);
94 final Calendar calendar = Calendar.getInstance(utcTimeZone);
95 final String timestamp = dateFormat.format(calendar.getTime());
96 return timestamp;
97 }
98
99 public static String prettyJson(Object obj) {
100 Gson gson = new Gson();
101 return prettyJson(gson.toJson(obj));
102 }
103
104 public static String prettyJson(String jsonString) {
105 JsonParser parser = new JsonParser();
106 Gson gson = new GsonBuilder().setPrettyPrinting().create();
107
108 JsonElement el = parser.parse(jsonString);
109 jsonString = gson.toJson(el);
110 return jsonString;
111 }
112
113 public static boolean isNumeric(String str) {
114 try {
115 double d = Long.parseLong(str);
116 } catch (NumberFormatException nfe) {
117 return false;
118 }
119 return true;
120 }
121
122 public static String createMd5(String input) {
123 try {
124 MessageDigest md5MsgDigest = MessageDigest.getInstance("MD5");
125 byte[] result = md5MsgDigest.digest((input).getBytes(StandardCharsets.UTF_8));
126 return Hex.encodeHexString(result);
127 } catch (NoSuchAlgorithmException ex) {
128 log.error(ex.getMessage());
129 return null;
130 }
131 }
132
133 public static String createHash(String algorithm, String input) {
134 try {
135// log.debug("input: " + input);
136 MessageDigest msgDigest = MessageDigest.getInstance(algorithm);
137 byte[] result = msgDigest.digest((input).getBytes(StandardCharsets.UTF_8));
138 StringBuilder sb = new StringBuilder();
139 for (int i = 0; i < result.length; i++) {
140 sb.append(Integer.toString((result[i] & 0xff) + 0x100, 16).substring(1));
141 }
142 return sb.toString();
143 } catch (NoSuchAlgorithmException ex) {
144 log.error(ex.getMessage());
145 return null;
146 }
147 }
148
149 public static String createBase64(String algorithm, String input) {
150 try {
151 log.debug("input=" + input);
152 byte[] enc = MessageDigest.getInstance(algorithm).digest(input.getBytes());
153 return new String(Base64.encodeBase64(enc));
154 } catch (Exception ex) {
155 ex.printStackTrace();
156 }
157 return "";
158 }
159
160 public static String createHash(byte[] input) {
161 if(input == null) return "";
162 StringBuilder sb = new StringBuilder();
163 for (int i = 0; i < input.length; i++) {
164 sb.append(Integer.toString((input[i] & 0xff) + 0x100, 16).substring(1));
165 }
166 return sb.toString();
167 }
168
169 public static String createHMAC(String hmacAlgoritm, String algoritm, String key, String input) {
170 try {
171 log.debug("input: " + input);
172 SecretKeySpec secretKey = new SecretKeySpec(key.getBytes("UTF-8"), hmacAlgoritm);
173 Mac algorithmHMAC = Mac.getInstance(hmacAlgoritm);
174 algorithmHMAC.init(secretKey);
175 byte[] macData = algorithmHMAC.doFinal(input.getBytes("UTF-8"));
176 return Hex.encodeHexString(macData);
177 } catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException ex) {
178 log.error(ex.getMessage());
179 return null;
180 }
181 }
182
183 public static String getPassword(String spId, String password, String timestamp) {
184 try {
185 String dgStr = spId + password + timestamp;
186
187 byte[] enc = MessageDigest.getInstance("SHA-256").digest(dgStr.getBytes());
188 return new String(Base64.encodeBase64(new String(Hex.encodeHex(enc)).toUpperCase().getBytes()));
189 } catch (NoSuchAlgorithmException ex) {
190 ex.printStackTrace();
191 }
192 return "";
193 }
194
195 public static byte[] encryptRSA(byte[] input, PublicKey publicKey) {
196 try {
197 Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
198 cipher.init(Cipher.ENCRYPT_MODE, publicKey);
199 return cipher.doFinal(input);
200 } catch (Exception ex) {
201 log.error(ex.getMessage());
202 return null;
203 }
204 }
205
206 public static String createRSABearerToken(String apiKey, String publicKey) {
207 try {
208 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
209 Cipher cipher = Cipher.getInstance("RSA");
210 byte[] decodedPublicKey = java.util.Base64.getDecoder().decode((publicKey));
211 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decodedPublicKey);
212 PublicKey pk = keyFactory.generatePublic(publicKeySpec);
213
214 cipher.init(Cipher.ENCRYPT_MODE, pk);
215 byte[] encryptedAPIKey = java.util.Base64.getEncoder().encode((cipher.doFinal(apiKey.getBytes("UTF-8"))));
216
217 return new String(encryptedAPIKey, "UTF-8");
218 } catch (Exception e) {
219 log.error("createRSABearerToken EXCEPTION: "+e.getMessage());
220 }
221
222 return null;
223 }
224
225 public static String getValue(String queryString, String requestedKey) {
226 String[] kvPairs = queryString.split("&");
227 for (String kvPair : kvPairs) {
228 String[] kv = kvPair.split("=");
229 String key = kv[0];
230 String value = kv[1];
231
232 if (key.equals(requestedKey)) {
233 return value;
234 }
235 }
236 return "";
237 }
238
239 public static void printQueryString(String queryString) {
240 String[] kvPairs = queryString.split("&");
241 for (String kvPair : kvPairs) {
242 String[] kv = kvPair.split("=");
243 String key = kv[0];
244 String value = kv[1];
245 log.debug("[" + key + "=" + value + "]");
246 }
247 }
248
249 public static String format2DecimalDigits(double amount) {
250 DecimalFormat formatter = new DecimalFormat("#0.00");
251 DecimalFormatSymbols custom = new DecimalFormatSymbols();
252 custom.setDecimalSeparator('.');
253 formatter.setDecimalFormatSymbols(custom);
254 return formatter.format(amount);
255 }
256
257 private static SSLContext getSSLContext(String jksLocation, String jksPassword) throws Exception {
258 KeyStore trustStore = KeyStore.getInstance("JKS");
259 trustStore.load(new FileInputStream(new File(jksLocation)), jksPassword.toCharArray());
260
261 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
262 kmf.init(trustStore, jksPassword.toCharArray());
263 KeyManager[] kms = kmf.getKeyManagers();
264
265 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
266 tmf.init(trustStore);
267 TrustManager[] tms = tmf.getTrustManagers();
268
269 SSLContext sslContext = SSLContext.getInstance("TLS");
270 sslContext.init(kms, tms, new SecureRandom());
271 return sslContext;
272
273 }
274
275 /**
276 * Kreiranje HTTPS klijenta sa koriscenjem javnog kljuca za klijentsku sertifikaciju
277 * @param properties
278 * @param propertiesKeys
279 * @param https_url
280 * @return
281 */
282 public static HttpsURLConnection createHttpsConnection(Properties properties, String[] propertiesKeys, String https_url) {
283 javax.net.ssl.SSLSocketFactory ssf = initCommunication(properties, propertiesKeys);
284 return createConnection(https_url, ssf);
285 }
286
287 private static javax.net.ssl.SSLSocketFactory initCommunication(Properties properties, String[] propertiesKeys) {
288 if (properties != null) {
289 try {
290// initSslFactory(new File(configProperties.getProperty("mtnTrustStore")), configProperties.getProperty("mtnTrustStorePassword"), new File(configProperties.getProperty("mtnClientStore")), configProperties.getProperty("mtnClientStorePassword"));
291 return initSslFactory(properties, propertiesKeys);
292 } catch (KeyStoreException ex) {
293 log.error("KeyStoreException", ex);
294 } catch (IOException ex) {
295 log.error("IOException", ex);
296 } catch (NoSuchAlgorithmException ex) {
297 log.error("NoSuchAlgorithmException", ex);
298 } catch (CertificateException ex) {
299 log.error("CertificateException", ex);
300 } catch (UnrecoverableKeyException ex) {
301 log.error("UnrecoverableKeyException", ex);
302 } catch (KeyManagementException ex) {
303 log.error("KeyManagementException", ex);
304 } catch (Exception ex) {
305 log.error("Exception", ex);
306 }
307 } else {
308 log.error("MTN properties file not loaded!!!");
309 }
310
311 return null;
312 }
313
314 public static javax.net.ssl.SSLSocketFactory initSslFactory(Properties properties, String[] propertiesKeys) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, Exception {
315 return initSslFactory(new File(properties.getProperty(propertiesKeys[0])), properties.getProperty(propertiesKeys[1]), new File(properties.getProperty(propertiesKeys[2])), properties.getProperty(propertiesKeys[3]));
316 }
317
318 public static javax.net.ssl.SSLSocketFactory initSslFactory(File trustStoreFile, String trustStorePasswd, File clientStoreFile, String clientStorePasswd) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, Exception {
319 KeyStore trustStore = KeyStore.getInstance("JKS");
320 KeyStore keyStore = KeyStore.getInstance("JKS");
321
322 return initSslFactory(trustStore, keyStore, trustStoreFile, trustStorePasswd, clientStoreFile, clientStorePasswd);
323 }
324
325 public static javax.net.ssl.SSLSocketFactory initSslFactory(KeyStore trustStore, KeyStore keyStore, File trustStoreFile, String trustStorePasswd, File clientStoreFile, String clientStorePasswd) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException, Exception {
326 SSLKey key = new SSLKey(trustStoreFile, trustStorePasswd, clientStoreFile, clientStorePasswd);
327 if (ssfCache.containsKey(key)) return ssfCache.get(key);
328
329 // inicijalizacija KeyStore objekta na osnovu JKS fajla.
330 FileInputStream inputStream = new FileInputStream(trustStoreFile);
331 trustStore.load(inputStream, trustStorePasswd.toCharArray());
332 inputStream.close();
333
334 // inicijalizacija KeyStore objekta na osnovu JKS fajla.
335 inputStream = new FileInputStream(clientStoreFile);
336 keyStore.load(inputStream, clientStorePasswd.toCharArray());
337 inputStream.close();
338
339 TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
340 tmf.init(trustStore);
341
342 KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
343 kmf.init(keyStore, clientStorePasswd.toCharArray());
344
345 SSLContext ctx = SSLContext.getInstance("TLS");
346 ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
347
348 // kreiranje SocketFactory objekta, koji je potrebno setovati HttpsURLConnection objektu.
349 javax.net.ssl.SSLSocketFactory ssf = ctx.getSocketFactory();
350 ssfCache.put(key, ssf);
351
352 return ssf;
353 }
354
355 public static HttpsURLConnection createConnection(String https_url, javax.net.ssl.SSLSocketFactory ssf) {
356 if (ssf == null) {
357 return null;
358 }
359 HttpsURLConnection con = null;
360 URL url;
361 try {
362 url = new URL(https_url);
363 con = (HttpsURLConnection) url.openConnection();
364 con.setSSLSocketFactory(ssf);
365 } catch (MalformedURLException e) {
366 log.error("MalformedURLException", e);
367 } catch (IOException e) {
368 log.error("IOException", e);
369 }
370 return con;
371 }
372
373 /**
374 * Dobijeno od Telenora
375 *
376 * @param toEncrypt
377 * @return
378 */
379 public static String encrypt(String toEncrypt, String key) {
380 String result = null;
381 log.debug("toEncrypt:" + toEncrypt);
382 byte[] keyArray = null;
383 byte[] toEncryptArray = null;
384
385 toEncryptArray = toEncrypt.getBytes();
386
387 try {
388 MessageDigest md = MessageDigest.getInstance("MD5");
389 keyArray = md.digest(key.getBytes());
390 } catch (NoSuchAlgorithmException e) {
391 log.debug(e.getMessage());
392 e.printStackTrace();
393 }
394
395 try {
396 Cipher c3des = Cipher.getInstance("DESede/ECB/PKCS5Padding");
397 byte[] keyBytes = Arrays.copyOf(keyArray, 24);
398 SecretKeySpec myKey = new SecretKeySpec(keyBytes, "DESede");
399 c3des.init(Cipher.ENCRYPT_MODE, myKey);
400 byte[] cipherText = c3des.doFinal(toEncryptArray);
401 result = Base64.encodeBase64String(cipherText);
402 } catch (Exception e) {
403 log.error(e.getMessage());
404 }
405
406 return result;
407 }
408
409 public static JsonObject parseToJson(String response) {
410 return (new JsonParser()).parse(response).getAsJsonObject();
411 }
412
413 public static Boolean convertToBoolean(String value) {
414 return Boolean.valueOf(value);
415 }
416
417 public static String createProtectedCustomerURI(Debit debit) {
418 if (debit==null || debit.getData() == null) return null;
419 StringJoiner sigJoiner = new StringJoiner("~");
420 sigJoiner.add(Double.toString(debit.getData().getAmount())).add(debit.getData().getCurrency()).add(Long.toString(debit.getID().getID()));
421 String signatureHashed = PaymentUtil.createHash("SHA-256", sigJoiner.toString());
422
423 String customPagesBaseUrl = debit != null && debit.getData()!=null ? (debit.getData().getParam(String.class, DebitCustomParams.CUSTOMER_CUSTOM_PAGES_BASE_URL, null)+debit.getID().getID()) : null;
424
425 UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(customPagesBaseUrl)
426 .queryParam("signature", signatureHashed)
427 .queryParam("algorithmSignature", "SHA256");
428 return uriBuilder.build(false).toUriString();
429 }
430
431 public static String customerURIWithParams(Debit debit, Map<String, String> queryParams) {
432 final String[] url = {createProtectedCustomerURI(debit)};
433 queryParams.forEach((k, v) -> {
434 url[0] += new StringBuilder().append("&").append(k).append("=").append(v).toString();
435 });
436 return url[0];
437 }
438
439 public static String encryptMessage(byte[] message, byte[] keyBytes)
440 throws InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException,
441 BadPaddingException, IllegalBlockSizeException {
442
443 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
444 SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
445 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
446 return Base64.encodeBase64String(cipher.doFinal(message));
447 }
448
449 public static String decryptMessage(String encryptedMessage, byte[] keyBytes)
450 throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
451 BadPaddingException, IllegalBlockSizeException {
452
453 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
454 SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
455 cipher.init(Cipher.DECRYPT_MODE, secretKey);
456 return new String(cipher.doFinal(Base64.decodeBase64(encryptedMessage)));
457 }
458
459 public static CardType getCardType(String cardNumber,String delimiter) {
460
461 if(delimiter != null && !delimiter.isEmpty()){
462 cardNumber = cardNumber.replace(delimiter,"");
463 }
464
465 for(CardType cardType:CardType.values()){
466 if(cardNumber.matches(cardType.getRegex()))
467 return cardType;
468 }
469 return CardType.UNKNOWN;
470 }
471
472 public static String getDelimiter(String maskedPan) {
473 for(char c : maskedPan.toCharArray()) {
474 if(c == '-') {
475 return String.valueOf(c);
476 }
477 if(c == '.') {
478 return String.valueOf(c);
479 }
480 }
481 return "";
482 }
483}
484
485<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
486<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
487<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
488<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
489<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
490<fmt:setBundle basename="monri_v2_messages"/>
491<html>
492 <head>
493 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
494 <script>
495
496
497 function submitForm() {
498 var paymentForm = document.getElementById("payment-form");
499 var whitelistedPanTokensField = document.getElementsByName("whitelisted_pan_tokens")[0];
500 var tokenizationValue = document.querySelector('input[name="whitelist"]:checked').value;
501 if (whitelistedPanTokensField) {
502 if (tokenizationValue === 'off') {
503 paymentForm.removeChild(whitelistedPanTokensField);
504 paymentForm.removeChild(document.getElementById("meridian_moto_channel_processing_data"));
505 } else {
506 whitelistedPanTokensField.value = tokenizationValue;
507 paymentForm.removeChild(document.getElementById("meridian_channel_processing_data"));
508 }
509 }else {
510 paymentForm.removeChild(document.getElementById("meridian_moto_channel_processing_data"));
511 }
512 paymentForm.submit();
513 }
514
515 function sendDeleteTokenRequest(val){
516 var buttonsContainer = document.getElementsByClassName("radio-btns")[0];
517 var clientCustomerId = document.getElementsByName("clientCustomerId")[0].value;
518 var deleteUrl = document.getElementsByName("deleteTokenUrl")[0].value;
519 var tokenPanValue = document.getElementsByName("tokenPan")[0].value;
520
521 $.ajax({
522 type: "post",
523 url: deleteUrl,
524 data: {clientCustomerId, tokenPanValue},
525 success: function(msg){
526 buttonsContainer.removeChild(document.getElementById(val));
527 },
528 statusCode: {
529 500: function(response) {
530 alert("Doslo je do greske!");
531 }
532 }
533 });
534 };
535
536 function handleClick(cb) {
537 var continueButton = document.getElementById("submit-btn");
538 var validationWarning = document.getElementsByClassName("validation-warning");
539 if (cb.checked && validationWarning.length === 0) {
540 continueButton.removeAttribute("disabled");
541 } else {
542 continueButton.setAttribute("disabled", "disabled")
543 }
544 }
545
546 </script>
547
548 <style>
549 body {
550 background-color: white;
551 color: rgb(61, 61, 61);
552 }
553 .visible-form{
554 margin: auto;
555 width: 70%;
556 padding: 10px;
557 }
558
559 hr{
560 border-color: #f1f1f1;
561 border-style: solid;
562 border-width: 1px;
563 }
564
565 #submit-btn{
566 width: 100px;
567 height: 40px;
568 background-color:#f1f1f1 ;
569 border: none;
570 text-align: center;
571 text-decoration: none;
572 font-size: 16px;
573 cursor: pointer;
574 }
575 /* label:hover{
576 color: lightgray;
577 } */
578 .form-radio
579 {
580 -webkit-appearance: none;
581 -moz-appearance: none;
582 appearance: none;
583 display: inline-block;
584 position: relative;
585 background-color: #f1f1f1;
586 top: 10px;
587 height: 30px;
588 width: 30px;
589 border: 0;
590 border-radius: 50px;
591 cursor: pointer;
592 margin-right: 7px;
593 outline: none;
594 }
595 .form-radio-small
596 {
597 height: 20px;
598 width: 20px;
599 }
600
601 .div-radio-btn{
602 padding-top: 0px;
603 padding-bottom: 15px;
604 }
605 .form-radio:checked::before
606 {
607 position: absolute;
608 font: 13px/1 'Open Sans', sans-serif;
609 left: 11px;
610 top: 7px;
611 content: '\02143';
612 transform: rotate(40deg);
613 }
614
615 .form-radio-small:checked::before
616 {
617 font-size: 10px;
618 left: 8px;
619 top: 4px;
620 }
621
622 .form-radio:hover
623 {
624 background-color: #f7f7f7;
625 }
626 .form-radio:checked
627 {
628 background-color: #f1f1f1;
629 }
630 label
631 {
632 color: #333;
633 -webkit-font-smoothing: antialiased;
634 -moz-osx-font-smoothing: grayscale;
635 cursor: pointer;
636 }
637
638 .min-deposit-amount{
639 text-align: right;
640 }
641
642 .validation-warning {
643 font-size: 90%;
644 background-color: #ffe;
645 border: 2px red dashed;
646 padding-left: 20px;
647 }
648
649 .deleteTokenButton {
650 box-shadow:inset 0px 1px 0px 0px #ffffff;
651 background:linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%);
652 background-color:#ffffff;
653 border-radius:6px;
654 border:1px solid #dcdcdc;
655 display:inline-block;
656 cursor:pointer;
657 color:#666666;
658 font-family:Arial;
659 font-size:15px;
660 font-weight:bold;
661 padding:6px 24px;
662 text-decoration:none;
663 text-shadow:0px 1px 0px #ffffff;
664 text-align: center;
665 margin-top: 10px;
666 margin-right: 5px;
667 clear: right;
668 float: right;
669 }
670 .deleteTokenButton:hover {
671 background:linear-gradient(to bottom, #f6f6f6 5%, #ffffff 100%);
672 background-color:#f6f6f6;
673 }
674 .deleteTokenButton:active {
675 position:relative;
676 top:1px;
677 }
678 .cardImage {
679 margin-left: 15px;
680 margin-right: 25px;
681 width: 50px;
682 height: auto;
683 vertical-align: middle;
684 margin-left: 5px;
685 }
686
687 .cardLogoSeparator {
688 display: inline-block;
689 width: 250px;
690 margin-left: 10px;
691 }
692 </style>
693 </head>
694
695
696 <body>
697
698 <div class="visible-form">
699
700 <c:if test="${not empty constraintViolations}">
701 <div class="validation-warning">
702 <p>
703 U vašem profilu nedostaju ili nisu ispravno unete sledeće informacije:
704 </p>
705 <ul>
706 <c:forEach var="violation" items="${constraintViolations}">
707 <c:set var="msg">monri_v2.${violation.propertyPath}</c:set>
708 <li><b><fmt:message key="${msg}"/></b> -> ${violation.message}</li>
709 </c:forEach>
710 </ul>
711 <p>
712 Podatke koji nedostaju unesite u sekciji <b>MOJ NALOG</b>, nakon toga pokušajte ponovo! Hvala.
713 </p>
714 </div>
715 </c:if>
716
717 <h4>Izaberite karticu sa kojom želite nastaviti:</h4>
718 <div class="radio-btns">
719 <c:if test="${customerTokens != null}">
720 <c:forEach var = "token" items="${customerTokens.tokens}" varStatus="loop">
721 <div id=${loop.index} class="div-radio-btn">
722 <div class="cardLogoSeparator">
723 <input type="radio" name="whitelist" value="${token.panToken}" class="form-radio" id="${token.panToken}">
724 <label for="${token.panToken}"><c:out value = "${token.maskedPan}"/></label>
725 </div>
726 <input class="deleteTokenButton" type="submit" value="Delete" onClick="sendDeleteTokenRequest('${loop.index}')" />
727 <img class ="cardImage" src="${token.cardImageUrl}" alt="cardImage">
728 <c:if test="${token != null}">
729 <input type="hidden" name="tokenPan" value="${token.panToken}"/>
730 </c:if>
731 <hr>
732 </div>
733 </c:forEach>
734 </c:if>
735 <div class="div-radio-btn">
736 <input type="radio" name="whitelist" value="off" class="form-radio" id="new-card" checked="checked">
737 <label for="new-card">Nova kartica</label>
738 </div>
739 <hr/>
740 </div>
741
742 <div class="min-deposit-amount">
743 <h4>Minimalan iznos uplate</h4>
744 <p><c:out value = "${minDepositAmount}"/> <c:out value = "${order.currency}"/></p>
745 </div>
746
747 <div style="text-align: right; padding-bottom: 5px">
748 <input type="checkbox" id="agree_with_terms_and_conditions" onclick="handleClick(this);" class="form-radio form-radio-small"/>
749 <label for="agree_with_terms_and_conditions">
750 Pročitao sam i slažem se sa <a href="${termsAndConditionsUrl}" target="_blank">uslovima korišćenja</a>.
751 </label>
752 </div>
753 <div style="text-align:right;">
754 <button id="submit-btn" onclick="submitForm()" disabled="disabled">Nastavi</button>
755 </div>
756 </div>
757
758 <div>
759 <form id="payment-form" action="${url}" method="POST">
760
761 <%-- Order Details --%>
762 <input type="hidden" name="order_number" value="${order.orderNumber}"/>
763 <input type="hidden" name="currency" value="${order.currency}"/>
764 <input type="hidden" name="amount" value="${order.amount}"/>
765 <input type="hidden" name="order_info" value="${order.orderInfo}"/>
766
767 <%--Processing Data--%>
768 <%--ip?--%>
769 <div id="meridian_channel_processing_data">
770 <input type="hidden" name="transaction_type" value="${processingDataMeridian.transactionType.label()}"/>
771 <input type="hidden" name="authenticity_token" value="${processingDataMeridian.authenticityToken}"/>
772 <input type="hidden" name="digest" value="${processingDataMeridian.digest}"/>
773 <input type="hidden" name="language" value="${processingDataMeridian.language.label()}"/>
774 </div>
775
776 <div id="meridian_moto_channel_processing_data">
777 <input type="hidden" name="transaction_type" value="${processingDataMeridianMoto.transactionType.label()}"/>
778 <input type="hidden" name="authenticity_token" value="${processingDataMeridianMoto.authenticityToken}"/>
779 <input type="hidden" name="digest" value="${processingDataMeridianMoto.digest}"/>
780 <input type="hidden" name="language" value="${processingDataMeridianMoto.language.label()}"/>
781 <input type="hidden" name="moto" value="true"/>
782 </div>
783
784 <%--Bayers Profile--%>
785 <input type="hidden" name="ch_full_name" value="${bayer.chFullName}"/>
786 <input type="hidden" name="ch_address" value="${bayer.chAddress}"/>
787 <input type="hidden" name="ch_city" value="${bayer.chCity}"/>
788 <input type="hidden" name="ch_zip" value="${bayer.chZip}"/>
789 <input type="hidden" name="ch_country" value="${bayer.chCountry}"/>
790 <input type="hidden" name="ch_phone" value="${bayer.chPhone}"/>
791 <input type="hidden" name="ch_email" value="${bayer.chEmail}"/>
792
793 <%-- Additional info --%>
794 <c:if test="${additionalInfo.tokenizePanOffered}">
795 <%--korisnik mora da odobri tokenizaciju--%>
796 <input type="hidden" name="tokenize_pan_offered" value="${additionalInfo.tokenizePanOffered}"/>
797 </c:if>
798
799 <c:if test="${additionalInfo.tokenizePan}">
800 <%--korisnik ne mora da odobri tokenizaciju--%>
801 <input type="hidden" name="tokenize_pan" value="true"/>
802 </c:if>
803
804 <c:if test="${customerTokens != null && not empty customerTokens.tokens}">
805 <input type="hidden" name="whitelisted_pan_tokens" value=""/>
806 </c:if>
807 <%--ukoliko zelimo da ogranicimo brand kartice--%>
808 <c:if test="${additionalInfo.tokenizeBrands != null && not empty additionalInfo.tokenizeBrands}">
809 <input type="hidden" name="tokenize_brands" value="${additionalInfo.tokenizeBrands}"/>
810 </c:if>
811
812 </form>
813 <c:if test="${clientCustomerId != null}">
814 <input type="hidden" name="clientCustomerId" value="${clientCustomerId}"/>
815 </c:if>
816 <c:if test="${deleteTokenUrl != null}">
817 <input type="hidden" name="deleteTokenUrl" value="${deleteTokenUrl}"/>
818 </c:if>
819 </div>
820
821 </body>
822</html>
823
824package rs.plusplusnt.midas.monri.controller;
825
826import javax.annotation.PostConstruct;
827import javax.crypto.BadPaddingException;
828import javax.crypto.IllegalBlockSizeException;
829import javax.crypto.NoSuchPaddingException;
830import javax.inject.Inject;
831
832import com.google.gson.Gson;
833import org.slf4j.Logger;
834import org.slf4j.LoggerFactory;
835import org.springframework.http.HttpStatus;
836import org.springframework.http.MediaType;
837import org.springframework.http.ResponseEntity;
838import org.springframework.stereotype.Controller;
839import org.springframework.util.MultiValueMap;
840import org.springframework.web.bind.annotation.*;
841import org.springframework.web.servlet.ModelAndView;
842import rs.plusplusnt.fullcircle.request.model.RequestID;
843import rs.plusplusnt.fullcircle.request.model.RequestManager;
844import rs.plusplusnt.midas.manager.impl.DefaultCustomerManager;
845import rs.plusplusnt.midas.monri.utils.MonriUtil;
846import rs.plusplusnt.midas.monri2.model.BayersProfile;
847import rs.plusplusnt.midas.monri2.model.Language;
848import rs.plusplusnt.midas.monri2.model.token.Monri2Token;
849import rs.plusplusnt.midas.monri2.model.token.Monri2Tokens;
850import rs.plusplusnt.midas.monri2.params.Monri2Constants;
851import rs.plusplusnt.midas.monri2.params.Monri2Params;
852import rs.plusplusnt.midas.primitives.Debit;
853import rs.plusplusnt.midas.primitives.DebitStates;
854import rs.plusplusnt.midas.service.DebitService;
855
856import java.security.InvalidKeyException;
857import java.security.NoSuchAlgorithmException;
858import java.util.ArrayList;
859import java.util.Arrays;
860import java.util.Iterator;
861import java.util.List;
862
863@Controller
864public class MonriV2FormController {
865
866 private static final Logger log = LoggerFactory.getLogger(MonriV2FormController.class);
867
868 @Inject
869 private DebitService debitService;
870 @Inject
871 private RequestManager requestManager;
872 @Inject
873 private DefaultCustomerManager defaultCustomerManager;
874
875 @PostConstruct
876 public void initCustomerInfo() throws Exception {
877 Monri2Token token1 = new Monri2Token();
878 token1.setMaskedPan("xxxx-XXXX-XXXX-5432");
879 token1.setPanToken("431413");
880 Monri2Token token2 = new Monri2Token();
881 token2.setMaskedPan("5500-0000-xxxx-0004");
882 token2.setPanToken("12313");
883 Monri2Token token3 = new Monri2Token();
884 token3.setMaskedPan("4123-xxxx-89012-345");
885 token3.setPanToken("532523");
886 Monri2Token token4 = new Monri2Token();
887 token4.setMaskedPan("x234-XXXX-XXXX-5432");
888 token4.setPanToken("5315135");
889
890 Monri2Tokens tokens = new Monri2Tokens();
891
892
893 tokens.setTokens(Arrays.asList(token1,token2,token3, token4));
894
895 defaultCustomerManager.setCustomerToken("Meridian", Monri2Constants.PAYMENT_PROVIDER, "98176", new Gson().toJson(tokens));
896 }
897
898 //ukoliko zelimo da korisnika redirektujemo na nasu stranicu
899 @GetMapping("providers/monriv2/success")
900 public ModelAndView success(
901 @RequestParam("acquirer") String acquirer,
902 @RequestParam("amount") Integer amount,
903 @RequestParam("approval_code") String approvalCode,
904 @RequestParam("authentication") String authentication,
905 @RequestParam("cc_type") String ccType,
906 @RequestParam("ch_full_name") String fullName,
907 @RequestParam("currency") String currency,
908 @RequestParam("enrollment") String enrollment,
909 @RequestParam("issuer") String issuer,
910 @RequestParam("language") String language,
911 @RequestParam("masked_pan") String maskedPan,
912 @RequestParam("number_of_installments") Integer numberOfInstallments,
913 @RequestParam("order_number") Long orderNumber,
914 @RequestParam(value = "pan_token",required = false) String panToken,
915 @RequestParam("response_code") String responseCode,
916 @RequestParam("digest") String digest
917 ) throws Exception {
918 Debit debit = debitService.get(new RequestID(orderNumber));
919 if (debit == null) {
920 log.error("Debit request is null!");
921 return new ModelAndView("monri2-server-error");
922 }
923 /*
924 Provera digest hash-a zbog sigurnosti!
925 Greška u dokumentaciji!
926 UriComponents uri = ServletUriComponentsBuilder
927 .fromCurrentRequest()
928 //.scheme("https")
929 .replaceQueryParam("digest")
930 .build();
931 if (!MonriUtil.checkDigest(debit, uri.toUri().toString(), digest)) {
932 log.error("Bad digest! Uri without digest:" + uri.toUri().toString() + ", digest:" + digest);
933 return new ModelAndView("monri2-server-error");
934 }*/
935 if (responseCode.equals(Monri2Constants.APPROVED)) {
936 log.debug("SUCCESS - orderNumber:" + orderNumber + ", responseCode: " + responseCode);
937 BayersProfile bayer = MonriUtil.getBayersProfile(debit);
938 final String clientTransactionId = debit.getData().getClientTransactionId();
939 final Double debitAmount = debit.getData().getAmount();
940 final String debitCurrency = debit.getData().getCurrency();
941 return new ModelAndView("monri2-success")
942 .addObject("bayer", bayer)
943 .addObject("orderInfo", MonriUtil.getStrParam(debit, Monri2Params.ORDER_INFO))
944 .addObject("transactionId", clientTransactionId)
945 .addObject("amount", debitAmount)
946 .addObject("currency", debitCurrency);
947 } else {
948 log.debug("ERROR - orderNumber:" + orderNumber + ", responseCode: " + responseCode);
949 return new ModelAndView("monri2-error");
950 }
951 }
952
953 //poziva se samo kada korisnik pozove "cancel" sa monri2 forme
954 @GetMapping("providers/monriv2/cancel")
955 public @ResponseBody ModelAndView cancel(@RequestParam("language") String language, @RequestParam("order_number") Long orderNumber) throws Exception {
956 log.debug("CANCEL - orderNumber:" + orderNumber);
957
958 Debit debit = debitService.get(new RequestID(orderNumber));
959
960 if(debit==null){
961 log.error("Debit request is null!");
962 }else if(DebitStates.isFinal(debit.getState())){
963 log.error("Debit request is already in final state: " + debit.getState());
964 }else{
965 requestManager.cancel(debit);
966 }
967
968 return new ModelAndView("monri2-cancel");
969 }
970
971 @PostMapping(value = "providers/monriv2/deleteToken", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
972 public ResponseEntity<Void> deleteSpecificToken(@RequestParam MultiValueMap<String, String> paramMap) {
973 try {
974 String clientCustomerId = paramMap.get("clientCustomerId").get(0);
975 String tokenPanValue = paramMap.get("tokenPanValue").get(0);
976
977 String tokens = defaultCustomerManager.getCustomerToken(Monri2Constants.COMPANY_NAME, Monri2Constants.PAYMENT_PROVIDER,clientCustomerId);
978 List<Monri2Token> allTokens = new Gson().fromJson(tokens, Monri2Tokens.class).getTokens();
979
980 Monri2Token monri2Token = new Monri2Token();
981 monri2Token.setPanToken(tokenPanValue);
982
983 Iterator<Monri2Token> tokenIterator = allTokens.iterator();
984
985 Monri2Token tokenToDelete = null;
986 while(tokenIterator.hasNext()) {
987 tokenToDelete = tokenIterator.next();
988 if(tokenToDelete.getPanToken().equals(monri2Token.getPanToken())) {
989 tokenIterator.remove();
990 break;
991 }
992 }
993
994 Monri2Tokens monriTokens = new Monri2Tokens(allTokens);
995 defaultCustomerManager.setCustomerToken(Monri2Constants.COMPANY_NAME, Monri2Constants.PAYMENT_PROVIDER, clientCustomerId, new Gson().toJson(monriTokens));
996
997 log.info("Successfully deleted token with pantoken: "+tokenPanValue);
998 } catch (Exception e) {
999 log.error("",e);
1000 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
1001 }
1002 return new ResponseEntity<>(HttpStatus.OK);
1003 }
1004
1005}
1006
1007package rs.plusplusnt.midas.primitives.utils;
1008
1009public enum CardType {
1010 MASTERCARD(CardType.baseUrl+"mastercard.svg","^((5[1-5])|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720))(.){12,14}$"),
1011 VISA(CardType.baseUrl+"visa.svg","^(4)(.){15}$"),
1012 AMERICAN_EXPRESS(CardType.baseUrl+"amex.svg", "^(37|34)(.){13}$"),
1013 MAESTRO(CardType.baseUrl+"maestro.svg", "^((5[6-9]|6[0-9])|50)(.){10,17}$"),
1014 DISCOVER(CardType.baseUrl+"discover.svg", "^(6)(.){15,18}$"),
1015 UNKNOWN(CardType.baseUrl+"notavailable.svg", ""),
1016 ;
1017
1018 private final String imageUrl;
1019 private final String regex;
1020
1021 public static final String baseUrl = "/midas/resources/providers/images/cards/";;
1022
1023 CardType(String imageUrl, String regex) {
1024 this.imageUrl = imageUrl;
1025 this.regex = regex;
1026 }
1027
1028 public String getImageUrl() {
1029 return imageUrl;
1030 }
1031
1032 public String getRegex(){
1033 return regex;
1034 }
1035}
1036package rs.plusplusnt.midas.monri.processor;
1037
1038import java.util.Collection;
1039import java.util.HashMap;
1040import java.util.HashSet;
1041import java.util.Map;
1042import java.util.Set;
1043import javax.annotation.PostConstruct;
1044import javax.inject.Inject;
1045import javax.inject.Named;
1046import javax.inject.Singleton;
1047import javax.validation.ConstraintViolation;
1048import javax.validation.Validation;
1049import javax.validation.Validator;
1050
1051import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
1052import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
1053import org.slf4j.Logger;
1054import org.slf4j.LoggerFactory;
1055import rs.plusplusnt.midas.monri.utils.MonriUtil;
1056import rs.plusplusnt.midas.monri2.params.Monri2Params;
1057import rs.plusplusnt.midas.monri2.model.BayersProfile;
1058import rs.plusplusnt.midas.monri2.model.Currency;
1059import rs.plusplusnt.midas.monri2.model.AdditionalInfo;
1060import rs.plusplusnt.midas.monri2.model.OrderDetails;
1061import rs.plusplusnt.midas.monri2.model.ProcessingData;
1062import rs.plusplusnt.midas.primitives.Credit;
1063import rs.plusplusnt.midas.primitives.CreditData;
1064import rs.plusplusnt.midas.primitives.Debit;
1065import rs.plusplusnt.midas.primitives.DebitData;
1066import rs.plusplusnt.midas.primitives.DebitStates;
1067import rs.plusplusnt.midas.primitives.PaymentProviderRegistrator;
1068import rs.plusplusnt.midas.primitives.ProviderProcessor;
1069import rs.plusplusnt.midas.primitives.TransferData;
1070import rs.plusplusnt.midas.primitives.builder.DebitDataBuilder;
1071import rs.plusplusnt.midas.primitives.request.DebitProcessorResponse;
1072import rs.plusplusnt.midas.primitives.request.ProcessorRequest;
1073import rs.plusplusnt.midas.primitives.request.ProcessorResponse;
1074import static rs.plusplusnt.midas.monri.utils.MonriUtil.*;
1075import rs.plusplusnt.midas.monri2.params.Monri2Constants;
1076import rs.plusplusnt.midas.primitives.utils.HttpUtils;
1077import rs.plusplusnt.midas.primitives.utils.PaymentUtil;
1078
1079/**
1080 *
1081 * @author miro
1082 */
1083@Named
1084@Singleton
1085public class MonriV2Processor implements ProviderProcessor {
1086
1087 /**
1088 * Kanali preko kojih ide uplata.
1089 * Meridian: za netokenizovane kartice
1090 * Meridian MOTO: za tokenizovane kartice
1091 */
1092 public enum MonriV2PaymentChannel{
1093 MERIDIAN,
1094 MERIDIAN_MOTO
1095 }
1096
1097 @Inject
1098 private PaymentProviderRegistrator paymentProviderRegistrator;
1099
1100 @Inject
1101 private HttpUtils httpUtils;
1102
1103 private static final Logger log = LoggerFactory.getLogger(MonriV2Processor.class);
1104
1105 @PostConstruct
1106 public void afterPropertiesSet() throws Exception {
1107 paymentProviderRegistrator.register("monri2", this);
1108 }
1109
1110 @Override
1111 public ProcessorResponse<DebitData> prepare(ProcessorRequest<Debit> request) throws Exception {
1112 log.debug("PREPARE!");
1113 Debit debit = request.getData();
1114 DebitDataBuilder debitDataBuilder = new DebitDataBuilder(debit.getData());
1115
1116 //validacija podataka
1117 Currency currency;
1118 Integer amount;
1119 try {
1120 currency = Currency.fromValue(debit.getData().getCurrency());
1121 amount = MonriUtil.getAmountInMinorUnits(currency, debit.getData().getAmount());
1122 } catch (RuntimeException e) {
1123 log.error(e.getMessage());
1124 return new DebitProcessorResponse(debitDataBuilder
1125 .error(e.getMessage())
1126 .build()
1127 , DebitStates.ERROR);
1128 }
1129
1130 OrderDetails orderDetails = getOrderDetails(debit, currency, amount);
1131 ProcessingData processingDataForMeridianChannel = getProcessingData(debit,
1132 orderDetails.getOrderNumber(),
1133 orderDetails.getAmount(),
1134 orderDetails.getCurrency(),
1135 MonriV2PaymentChannel.MERIDIAN);
1136 ProcessingData processingDataForMeridinMotoChannel = getProcessingData(debit,
1137 orderDetails.getOrderNumber(),
1138 orderDetails.getAmount(),
1139 orderDetails.getCurrency(),
1140 MonriV2PaymentChannel.MERIDIAN_MOTO);
1141 BayersProfile bayersProfile = getBayersProfile(debit);
1142 AdditionalInfo additionalInfo = getAdditionalInfo(debit);
1143
1144 Set<ConstraintViolation<Object>> constraintViolations = validate(orderDetails,
1145 processingDataForMeridianChannel,
1146 processingDataForMeridinMotoChannel,
1147 bayersProfile,
1148 additionalInfo);
1149
1150 if (!constraintViolations.isEmpty()) {
1151 constraintViolations.forEach(v -> log.warn(v.toString()));
1152 }
1153 //validacija uspesna
1154 return new DebitProcessorResponse(debitDataBuilder
1155 .customerUrl(PaymentUtil.createProtectedCustomerURI(debit))
1156 .build()
1157 , DebitStates.CHECK);
1158 }
1159
1160 @Override
1161 public ProcessorResponse<DebitData> debit(ProcessorRequest<Debit> request) throws Exception {
1162 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1163 }
1164
1165 @Override
1166 public ProcessorResponse<DebitData> check(ProcessorRequest<Debit> request) throws Exception {
1167 //Ne postoji mogucnost provere stanja transakcije kod Monri2 PP,
1168 //cekamo notifikaciju!
1169 log.debug("CHECK!");
1170 return new DebitProcessorResponse(request.getData().getData(), DebitStates.CHECK);
1171 }
1172
1173 @Override
1174 public Collection<TransferData> createTransfer(Debit debit) throws Exception {
1175 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1176 }
1177
1178 @Override
1179 public Collection<TransferData> createTransfer(Credit credit) throws Exception {
1180 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1181 }
1182
1183 @Override
1184 public boolean isDoDebit(Debit debit) throws Exception {
1185 return false;
1186 }
1187
1188 @Override
1189 public boolean isComplited(Debit debit) throws Exception {
1190 return false;
1191 }
1192
1193 @Override
1194 public String createUrl(Debit debit, URL_TYPE urlType) throws Exception {
1195 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1196 }
1197
1198 @Override
1199 public DebitData notify(Debit debit, Map<String, String[]> params) throws Exception {
1200 log.debug("NOTIFY!");
1201 String responseCode = params.get("responseCode")[0];
1202 String responseMessage = params.get("responseMessage")[0];
1203 String panToken = params.get("panToken")[0];
1204 String maskedPan = params.get("maskedPan")[0];//@todo
1205
1206 if (responseCode.equals(Monri2Constants.APPROVED)) {
1207 log.debug("APPROVED: " + responseMessage);
1208 log.debug("PAN TOKEN: " + panToken + " - FOR: " + maskedPan);
1209
1210 try{
1211 if (panToken != null && !panToken.isEmpty())
1212 addNewCustomerToken(debit, panToken, maskedPan);
1213 }catch(Exception e){
1214 log.error("Exception in addNewCustomerToken method | exc:"+e);
1215 }
1216
1217 debit.setState(DebitStates.CREATE_TRANSFER);
1218 } else {
1219 log.debug("FAILED: " + responseMessage);
1220 debit.setError(responseMessage);
1221 debit.setState(DebitStates.FAILED);
1222 }
1223
1224 return debit.getData();
1225 }
1226
1227 @Override
1228 public DebitProcessorResponse debitFormGet(Debit debit, Map<String, String[]> queryParams) throws Exception {
1229 log.debug("DEBIT FORM GET!");
1230
1231 Currency currency = Currency.fromValue(debit.getData().getCurrency());
1232 Integer amount = MonriUtil.getAmountInMinorUnits(currency, debit.getData().getAmount());
1233
1234 OrderDetails orderDetails = getOrderDetails(debit, currency, amount);
1235 ProcessingData processingDataForMeridianChannel = getProcessingData(debit,
1236 orderDetails.getOrderNumber(),
1237 orderDetails.getAmount(),
1238 orderDetails.getCurrency(),
1239 MonriV2PaymentChannel.MERIDIAN);
1240 ProcessingData processingDataForMeridianMotoChannel = getProcessingData(debit,
1241 orderDetails.getOrderNumber(),
1242 orderDetails.getAmount(),
1243 orderDetails.getCurrency(),
1244 MonriV2PaymentChannel.MERIDIAN_MOTO);
1245 AdditionalInfo additionalInfo = getAdditionalInfo(debit);
1246 BayersProfile bayersProfile = getBayersProfile(debit);
1247
1248 Set<ConstraintViolation<Object>> constraintViolations = validate(orderDetails,
1249 processingDataForMeridianChannel,
1250 processingDataForMeridianMotoChannel,
1251 bayersProfile,
1252 additionalInfo);
1253
1254 log.debug("Customer tokens: " + debit.getData().getCustomerToken());
1255 constraintViolations.forEach(cv -> log.warn("violation: " + cv.getMessage()));
1256
1257 DebitProcessorResponse response = new DebitProcessorResponse();
1258 Map<String, Object> model = new HashMap();
1259 model.put("customerTokens", getCustomerTokens(debit));
1260 model.put("url", getStrParam(debit, Monri2Params.FORM_ENDPOINT));
1261 model.put("order", orderDetails);
1262 model.put("processingDataMeridian", processingDataForMeridianChannel);
1263 model.put("processingDataMeridianMoto", processingDataForMeridianMotoChannel);
1264 model.put("bayer", bayersProfile);
1265 model.put("additionalInfo", additionalInfo);
1266 model.put("minDepositAmount", getStrParam(debit, Monri2Params.MIN_DEPOSIT_AMOUNT));
1267 model.put("termsAndConditionsUrl", getStrParam(debit, Monri2Params.TERMS_AND_CONDITIONS_URL, Monri2Constants.TERMS_AND_CONDITIONS_DEAFULT_URL));
1268 model.put("constraintViolations", constraintViolations);
1269 model.put("clientCustomerId", debit.getData().getClientCustomerId());
1270 model.put("deleteTokenUrl", httpUtils.getBaseUrl().endsWith("/") ? httpUtils.getBaseUrl() + "midas/providers/monriv2/deleteToken" : httpUtils.getBaseUrl() + "/midas/providers/monriv2/deleteToken");
1271 response.setModel(model);
1272 response.setView("monri2-payment-form");
1273
1274 return response;
1275 }
1276
1277 @Override
1278 public DebitProcessorResponse debitFormPost(Debit debit, Map<String, String[]> queryParams) throws Exception {
1279 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1280 }
1281
1282 @Override
1283 public ProcessorResponse<CreditData> credit(ProcessorRequest<Credit> request) throws Exception {
1284 throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
1285 }
1286
1287 private Set<ConstraintViolation<Object>> validate(OrderDetails orderDetails, ProcessingData processingData, ProcessingData processingDataMoto, BayersProfile bayersProfile, AdditionalInfo additionalInfo) {
1288 Validator validator = Validation.byDefaultProvider()
1289 .configure()
1290 .messageInterpolator(
1291 new ResourceBundleMessageInterpolator(
1292 new PlatformResourceBundleLocator( "monri_v2_messages" )
1293 )
1294 )
1295 .buildValidatorFactory()
1296 .getValidator();
1297 Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
1298 constraintViolations.addAll(validator.validate(orderDetails));
1299 constraintViolations.addAll(validator.validate(processingData));
1300 constraintViolations.addAll(validator.validate(processingDataMoto));
1301 constraintViolations.addAll(validator.validate(bayersProfile));
1302 constraintViolations.addAll(validator.validate(additionalInfo));
1303 return constraintViolations;
1304 }
1305}
1306
1307package rs.plusplusnt.midas.monri2.model.token;
1308
1309import rs.plusplusnt.midas.primitives.utils.PaymentUtil;
1310
1311import java.util.Objects;
1312
1313/**
1314 *
1315 * @author miro
1316 */
1317public class Monri2Token {
1318
1319 private String panToken;
1320 private String maskedPan;
1321 private String cardImageUrl;
1322
1323 public Monri2Token() {
1324 }
1325
1326 public Monri2Token(String panToken, String maskedPan) {
1327 this.panToken = panToken;
1328 this.maskedPan = maskedPan;
1329 this.cardImageUrl = PaymentUtil.getCardType(maskedPan,PaymentUtil.getDelimiter(maskedPan)).getImageUrl();
1330 }
1331
1332 public String getPanToken() {
1333 return panToken;
1334 }
1335
1336 public void setPanToken(String panToken) {
1337 this.panToken = panToken;
1338 }
1339
1340 public String getMaskedPan() {
1341 return maskedPan;
1342 }
1343
1344 public void setMaskedPan(String maskedPan) {
1345 this.maskedPan = maskedPan;
1346 this.cardImageUrl = PaymentUtil.getCardType(maskedPan,PaymentUtil.getDelimiter(maskedPan)).getImageUrl();
1347 }
1348
1349 public void setCardImageUrl(String cardImageUrl) {
1350 this.cardImageUrl = cardImageUrl;
1351 }
1352
1353 public String getCardImageUrl() {
1354 return cardImageUrl;
1355 }
1356
1357 @Override
1358 public String toString() {
1359 return "Monri2Token{" + "panToken=" + panToken + ", maskedPan=" + maskedPan + '}';
1360 }
1361
1362 @Override
1363 public int hashCode() {
1364 int hash = 7;
1365 hash = 83 * hash + Objects.hashCode(this.panToken);
1366 return hash;
1367 }
1368
1369 @Override
1370 public boolean equals(Object obj) {
1371 if (this == obj) {
1372 return true;
1373 }
1374 if (obj == null) {
1375 return false;
1376 }
1377 if (getClass() != obj.getClass()) {
1378 return false;
1379 }
1380 final Monri2Token other = (Monri2Token) obj;
1381 if (!Objects.equals(this.panToken, other.panToken)) {
1382 return false;
1383 }
1384 return true;
1385 }
1386}
1387package rs.plusplusnt.midas.monri.utils;
1388
1389import com.google.common.hash.Hashing;
1390import com.google.gson.Gson;
1391import java.math.BigDecimal;
1392import java.math.RoundingMode;
1393import java.nio.charset.StandardCharsets;
1394
1395import org.slf4j.Logger;
1396import org.slf4j.LoggerFactory;
1397import rs.plusplusnt.midas.monri.processor.MonriV2Processor;
1398import rs.plusplusnt.midas.monri.processor.MonriV2Processor.MonriV2PaymentChannel;
1399import rs.plusplusnt.midas.monri2.model.AdditionalInfo;
1400import rs.plusplusnt.midas.monri2.model.BayersProfile;
1401import rs.plusplusnt.midas.monri2.model.Currency;
1402import rs.plusplusnt.midas.monri2.model.Language;
1403import rs.plusplusnt.midas.monri2.model.OrderDetails;
1404import rs.plusplusnt.midas.monri2.model.ProcessingData;
1405import rs.plusplusnt.midas.monri2.model.TransactionType;
1406import rs.plusplusnt.midas.monri2.model.token.Monri2Token;
1407import rs.plusplusnt.midas.monri2.model.token.Monri2Tokens;
1408import rs.plusplusnt.midas.monri2.params.Monri2Params;
1409import rs.plusplusnt.midas.primitives.Debit;
1410import rs.plusplusnt.midas.primitives.DebitCustomParams;
1411
1412/**
1413 *
1414 * @author miro
1415 */
1416public class MonriUtil {
1417
1418 private static final Logger log = LoggerFactory.getLogger(MonriUtil.class);
1419 public static final String DEFDAULT_COUNTRY_VALUE = "Srbija";
1420 public static final String DEFAULT_ZIP_VALUE = "11000";
1421
1422 /**
1423 * Vraca iznos u minor units formatu. example: 10.24 USD is 1024
1424 *
1425 * @param currency dozvoljena Monri valuta
1426 * @param amount iznos
1427 * @return iznos u minor units formatu
1428 */
1429 public static Integer getAmountInMinorUnits(Currency currency, double amount) throws RuntimeException {
1430 if (currency == null) {
1431 throw new IllegalArgumentException("Currency cannot be null");
1432 }
1433 try {
1434 int minorUnits = java.util.Currency.getInstance(currency.label()).getDefaultFractionDigits();
1435 BigDecimal roundedAmount = new BigDecimal(amount).setScale(minorUnits, RoundingMode.HALF_DOWN);
1436 String amountInMinotUnits = (roundedAmount + "").replace(".", "");
1437 return Integer.parseInt(amountInMinotUnits);
1438 } catch (NumberFormatException e) {
1439 throw new NumberFormatException("Unsupported amount:" + amount);
1440 }
1441 }
1442
1443 /**
1444 * Vraca string reprezentaciju debit custom parametra ili konketenizaciju
1445 * vise njih
1446 *
1447 * @param debit debit
1448 * @param params naziv/i parametara
1449 * @return
1450 */
1451 public static String getStrCustomParam(Debit debit, String... params) {
1452 if (debit == null || debit.getData() == null || debit.getData().getCustomParams() == null) {
1453 return "";
1454 }
1455 String result = "";
1456 for (String param : params) {
1457 String value = debit.getData().getCustomParam(String.class, param, "");
1458 result += value != null ? (value + " ") : "";
1459 }
1460 return result.trim();
1461 }
1462
1463 /**
1464 * Vraca boolean reprezentaciju debit parametra. Parametar se u
1465 * konfiguraciji cuva kao broj(0-false, ostalo-true)
1466 *
1467 * @param debit debit
1468 * @param param naziv parametra
1469 * @param defaultValue podrazumevana vrednost
1470 * @return
1471 */
1472 public static boolean getBooleanParam(Debit debit, String param, boolean defaultValue) {
1473 if (debit == null || debit.getData() == null || debit.getData().getParams() == null || param == null) {
1474 return defaultValue;
1475 }
1476 return debit.getData().getParam(Integer.class, param, defaultValue ? 1 : 0) != 0;
1477 }
1478
1479 /**
1480 * Vraca string reprezentaciju debit parametra. default: ""
1481 *
1482 * @param debit debit
1483 * @param param naziv parametra
1484 * @return
1485 */
1486 public static String getStrParam(Debit debit, String param) {
1487 return getStrParam(debit, param, "");
1488 }
1489
1490 /**
1491 * Vraca string reprezentaciju debit parametra
1492 *
1493 * @param debit debit
1494 * @param param naziv parametra
1495 * @param defaultValue podrazumevana vrednost
1496 * @return
1497 */
1498 public static String getStrParam(Debit debit, String param, String defaultValue) {
1499 if (debit == null || debit.getData() == null || debit.getData().getParams() == null || param == null) {
1500 return defaultValue;
1501 }
1502 return debit.getData().getParam(String.class, param, defaultValue);
1503 }
1504
1505 /**
1506 * Dodaje novi token.
1507 * Tokeni se se cuvaju kao json. Tokeni ce biti sacuvani prilikom save/update
1508 * debit zahteva.
1509 * @param debit debit zahtev
1510 * @param panToken token koji dobijamo od PP
1511 * @param maskedPan broj platne kartice
1512 */
1513 public static void addNewCustomerToken(Debit debit, String panToken, String maskedPan) { //err
1514 if(debit==null || debit.getData()==null || panToken==null){
1515 throw new IllegalArgumentException();
1516 }
1517 Gson gson = new Gson();
1518 String jsonTokens = debit.getData().getCustomerToken();
1519 Monri2Token newToken = new Monri2Token(panToken, maskedPan);
1520
1521 Monri2Tokens customerTokens;
1522 if (jsonTokens == null || jsonTokens.isEmpty()) {
1523 customerTokens = new Monri2Tokens();
1524 } else {
1525 customerTokens = gson.fromJson(jsonTokens, Monri2Tokens.class);
1526 for(Monri2Token token:customerTokens.getTokens()){
1527 if(token.getPanToken().equals(panToken)){
1528 return;
1529 }
1530 if(token.getMaskedPan().equals(maskedPan)){
1531 token.setPanToken(panToken);
1532 debit.getData().setCustomerToken(gson.toJson(customerTokens));
1533 return;
1534 }
1535 }
1536 }
1537 customerTokens.getTokens().add(newToken);
1538 debit.getData().setCustomerToken(gson.toJson(customerTokens));
1539 }
1540
1541 /**
1542 * Vraca tokene specificne za korisnika koji je iniciraio debit zahtev
1543 * @param debit debit zahtev
1544 * @return
1545 */
1546 public static Monri2Tokens getCustomerTokens(Debit debit) { //err
1547 try {
1548 return new Gson().fromJson(debit.getData().getCustomerToken(), Monri2Tokens.class);
1549 } catch (Exception e) {
1550 log.error("Exception in getCustomerTokens method | exc:"+e);
1551 return null;
1552 }
1553 }
1554
1555 public static BayersProfile getBayersProfile(Debit debit) {
1556
1557 String chCountryValue = getStrCustomParam(debit, DebitCustomParams.CUSTOMER_COUNTRY);
1558 String chZipValue = getStrCustomParam(debit, DebitCustomParams.CUSTOMER_POSTAL_CODE);
1559
1560 // fix defaults here, player cannot change these
1561 if (chCountryValue.length() < 3) {
1562 chCountryValue = DEFDAULT_COUNTRY_VALUE;
1563 }
1564
1565 if (chZipValue.length() < 3) {
1566 chZipValue = DEFAULT_ZIP_VALUE;
1567 }
1568
1569 BayersProfile bayer = new BayersProfile();
1570 bayer.setChFullName(getStrCustomParam(debit, DebitCustomParams.CUSTOMER_FIRST_NAME, DebitCustomParams.CUSTOMER_LAST_NAME));
1571 bayer.setChCity(getStrCustomParam(debit, DebitCustomParams.CUSTOMER_CITY));
1572 bayer.setChAddress(getStrCustomParam(debit, DebitCustomParams.CUSTOMER_STREET_NAME, DebitCustomParams.CUSTOMER_HOUSE_NUMBER));
1573 bayer.setChZip(chZipValue);
1574 bayer.setChCountry(chCountryValue);
1575 bayer.setChEmail(getStrCustomParam(debit, DebitCustomParams.CUSTOMER_EMAIL));
1576 bayer.setChPhone(getStrCustomParam(debit, DebitCustomParams.CUSTOMER_PHONE));
1577
1578 return bayer;
1579 }
1580
1581 public static AdditionalInfo getAdditionalInfo(Debit debit) {
1582 AdditionalInfo additionalInfo = new AdditionalInfo();
1583 additionalInfo.setTokenizePanOffered(getBooleanParam(debit, Monri2Params.TOKENIZE_PAN_OFFERED, true));
1584 additionalInfo.setTokenizePan(getBooleanParam(debit, Monri2Params.TOKENIZE_PAN, false));
1585 additionalInfo.setTokenizeBrands(getStrParam(debit, Monri2Params.TOKENIZE_BRANDS, null));
1586 additionalInfo.setWhitelistedPanTokens(null); //korisnik bira zeljenu karticu na formi
1587 return additionalInfo;
1588 }
1589
1590 public static OrderDetails getOrderDetails(Debit debit, Currency currency, Integer amount) {
1591 OrderDetails orderDetails = new OrderDetails();
1592 orderDetails.setOrderNumber(String.valueOf(debit.getId()));
1593 orderDetails.setCurrency(currency);
1594 orderDetails.setAmount(amount);
1595 orderDetails.setOrderInfo(getStrParam(debit, Monri2Params.ORDER_INFO)); // internacionalizacija
1596 return orderDetails;
1597 }
1598
1599 public static ProcessingData getProcessingData(Debit debit, String orderNumber, Integer amount, Currency currency, MonriV2Processor.MonriV2PaymentChannel channel) {
1600
1601 String apiKey;
1602 String authToken;
1603 if (channel == MonriV2PaymentChannel.MERIDIAN_MOTO) {
1604 apiKey = getStrParam(debit, Monri2Params.API_KEY_MOTO);
1605 authToken = getStrParam(debit, Monri2Params.AUTH_TOKEN_MOTO);
1606 } else {
1607 apiKey = getStrParam(debit, Monri2Params.API_KEY);
1608 authToken = getStrParam(debit, Monri2Params.AUTH_TOKEN);
1609 }
1610
1611 final String digest = Hashing.
1612 sha512().
1613 hashString(apiKey + orderNumber + amount + currency, StandardCharsets.UTF_8).toString();
1614
1615 final ProcessingData processingData = new ProcessingData();
1616 final String languageValue = debit.getData().getCustomParam(String.class, "clientLanguage", "sr");
1617 processingData.setLanguage(getLanguage(languageValue));
1618 processingData.setTransactionType(TransactionType.PURCHASE);
1619 processingData.setAuthenticityToken(authToken);
1620 processingData.setDigest(digest);
1621 return processingData;
1622 }
1623
1624 private static Language getLanguage(String languageValue) {
1625 for (Language lang: Language.values()) {
1626 if (lang.label().equals(languageValue)) return lang;
1627 }
1628 return Language.SR;
1629 }
1630
1631 public static boolean checkDigest(Debit debit, String uri, String digest) {
1632 String apiKey = getStrParam(debit, Monri2Params.API_KEY);
1633 return digest.equals(Hashing.sha512().hashString(apiKey + uri, StandardCharsets.UTF_8).toString());
1634 }
1635}