diff --git a/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmount.java b/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmount.java index 714bbbebab..897c4e21a3 100644 --- a/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmount.java +++ b/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmount.java @@ -23,6 +23,10 @@ import java.math.BigDecimal; import java.util.Objects; +/** + * @deprecated Use {@link org.jpos.cmf.CMFAdditionalAmount} + */ +@Deprecated @SuppressWarnings("WeakerAccess") public class AdditionalAmount { @@ -111,16 +115,12 @@ public String serialize() { long absAmt= getAmount().movePointRight(getCurrencyMinorUnit()).abs().longValue(); - @SuppressWarnings("StringBufferReplaceableByString") - StringBuilder sb = new StringBuilder(); - sb.append(getAccountType()); - sb.append(getAmountType().toString()); - sb.append(getCurrencyCode()); - sb.append(getCurrencyMinorUnit()); - sb.append(getAmount().compareTo(BigDecimal.ZERO) >= 0 ? "C" : "D"); - sb.append(StringUtils.leftPad(Long.toString(absAmt), 12, '0')); - - return sb.toString(); + return getAccountType() + + getAmountType().toString() + + getCurrencyCode() + + getCurrencyMinorUnit() + + (getAmount().compareTo(BigDecimal.ZERO) >= 0 ? "C" : "D") + + StringUtils.leftPad(Long.toString(absAmt), 12, '0'); } @Override diff --git a/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmountsWrapper.java b/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmountsWrapper.java index 213121ecba..22dc19b43a 100644 --- a/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmountsWrapper.java +++ b/modules/cmf/src/main/java/org/jpos/cmf/AdditionalAmountsWrapper.java @@ -27,7 +27,10 @@ /** * Handles additional amounts field content - DE-054 + * + * @deprecated Use {@link org.jpos.iso.AdditionalAmountsWrapper} */ +@Deprecated public final class AdditionalAmountsWrapper extends LinkedHashSet { private static final long serialVersionUID = 2526355280704001241L; diff --git a/modules/cmf/src/main/java/org/jpos/cmf/AmountType.java b/modules/cmf/src/main/java/org/jpos/cmf/AmountType.java index b796acc319..adfec3140e 100644 --- a/modules/cmf/src/main/java/org/jpos/cmf/AmountType.java +++ b/modules/cmf/src/main/java/org/jpos/cmf/AmountType.java @@ -1,6 +1,6 @@ /* * jPOS Project [http://jpos.org] - * Copyright (C) 2000-2021 jPOS Software SRL + * Copyright (C) 2000-2023 jPOS Software SRL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -18,18 +18,21 @@ package org.jpos.cmf; +import org.jpos.iso.AdditionalAmountType; +import org.jpos.iso.AdditionalAmountTypeConverter; + import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * See jPOS-CMF.pdf DE-054
- * Some extra entries are based on ISO-8583:2003 + * Some extra entries are based on ISO-8583:2003 and other common specs. */ -public enum AmountType { +public enum AmountType implements AdditionalAmountType { ISO_RESERVED("00"), - // Account related balances + // 0x..1x - Account related balances ACCOUNT_LEDGER_CURRENT_BALANCE("01"), ACCOUNT_AVAILABLE_BALANCE("02"), AMOUNT_OWING("03"), @@ -40,6 +43,7 @@ public enum AmountType { DESTINATION_ACCOUNT_AVAILABLE_BALANCE("08"), CREDIT_LINE("09"), AMOUNT_ON_HOLD("10"), + PREPAID_ONLINE_BILL_FEE("17"), // Mastercard // 2x - Card related amounts AMOUNT_REMAINING_THIS_CYCLE("20"), @@ -48,17 +52,32 @@ public enum AmountType { AMOUNT_CASH("40"), AMOUNT_GOODS_AND_SERVICES("41"), AMOUNT_SURCHARGE("42"), + TOTAL_CUMULATIVE_AMOUNT("43"), // Visa: total cumulative, for series of incremental transactions + AMOUNT_PRE_CURRENCY_CONVERSION("45"), // Visa // 5x - Electronic benefit amounts BEGINNING_BALANCE("50"), PRE_AUTH_AMOUNT("51"), + CLIENT_PROVIDED_FEES("56"), // Visa + + // Visa, Mastercard, others, usually used for partials. + // Left as reference/placeholder/pragmatism here, but in jPOS-CMF is more appropriate to use DE-030 + ORIGINAL_AMOUNT("57"), - // custom CMF + // other custom mappings GRATUITY("80"), AMOUNT_TAXABLE("81"), + TRANSIT_AMOUNT("4T"), // Visa + // HEALTHCARE USA + HEALTHCARE_AMOUNT_COPAYMENT("3S"), // Visa + HEALTHCARE_AMOUNT_ELIGIBILITY("4S"), // Mastercard: 10 + HEALTHCARE_AMOUNT_PRESCRIPTION("4U"), // Mastercard: 11 + HEALTHCARE_AMOUNT_VISION("4V"), // Mastercard: 12 + HEALTHCARE_AMOUNT_CLINIC("4W"), // Visa: clinic/other qualified medical + HEALTHCARE_AMOUNT_DENTAL("4X"), // Visa - // PRIVATE RESERVED + // OTHER PRIVATE RESERVED (may be repurposed/renamed in the future) PRIVATE_RESERVED_1S("1S"), PRIVATE_RESERVED_1T("1T"), PRIVATE_RESERVED_1U("1U"), @@ -77,7 +96,6 @@ public enum AmountType { PRIVATE_RESERVED_2Y("2Y"), PRIVATE_RESERVED_2Z("2Z"), - PRIVATE_RESERVED_3S("3S"), PRIVATE_RESERVED_3T("3T"), PRIVATE_RESERVED_3U("3U"), PRIVATE_RESERVED_3V("3V"), @@ -86,12 +104,6 @@ public enum AmountType { PRIVATE_RESERVED_3Y("3Y"), PRIVATE_RESERVED_3Z("3Z"), - PRIVATE_RESERVED_4S("4S"), - PRIVATE_RESERVED_4T("4T"), - PRIVATE_RESERVED_4U("4U"), - PRIVATE_RESERVED_4V("4V"), - PRIVATE_RESERVED_4W("4W"), - PRIVATE_RESERVED_4X("4X"), PRIVATE_RESERVED_4Y("4Y"), PRIVATE_RESERVED_4Z("4Z"), @@ -118,18 +130,40 @@ public enum AmountType { this.code = code; } + public String getCode() { + return code; + } + + /** shorter alias for getCode, in the style of enum name(), required by {@link AdditionalAmountType} */ @Override - public String toString() { + public String code() { return code; } - public String getCode() { + + @Override + public String toString() { return code; } public static AmountType fromCode(String code) { Objects.requireNonNull(code); - AmountType ret = byCode.get(code.toUpperCase()); - if (ret == null) throw new IllegalArgumentException("Invalid amount type: " + code); - return ret; + return byCode.get(code.toUpperCase()); } + + + // ----- inner converter (dummy, since this is from CMF to CMF, but left here as reference implementation) + + public static AdditionalAmountTypeConverter CONVERTER = new AdditionalAmountTypeConverter() { + public String toCMF(String code) { + Objects.requireNonNull(code); + AmountType t = byCode.get(code.toUpperCase()); + return t != null ? t.code() : null; + } + + public String fromCMF(String cmfCode) { + Objects.requireNonNull(cmfCode); + AmountType t = byCode.get(cmfCode.toUpperCase()); + return t != null ? t.code() : null; + } + }; } diff --git a/modules/cmf/src/main/java/org/jpos/cmf/CMFAdditionalAmount.java b/modules/cmf/src/main/java/org/jpos/cmf/CMFAdditionalAmount.java new file mode 100644 index 0000000000..1f2c76edd2 --- /dev/null +++ b/modules/cmf/src/main/java/org/jpos/cmf/CMFAdditionalAmount.java @@ -0,0 +1,83 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2023 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.cmf; + +import org.apache.commons.lang3.StringUtils; +import org.jpos.iso.AdditionalAmount; + +import java.math.BigDecimal; +import java.util.Objects; + +/** + * Represents one occurrence of an Additional Amount (from DE-54) in jPOS-CMF format. + */ +public class CMFAdditionalAmount extends AdditionalAmount { + + public final static int SERIALIZED_DATA_LENGTH = 21; + + public CMFAdditionalAmount() { + } + + public CMFAdditionalAmount(String accountType, BigDecimal amount, String currencyCode, + String amountType, int currencyMinorUnit) { + super(accountType, amount, currencyCode, amountType, currencyMinorUnit); + } + + @Override + public String serialize() { + if (getAmountTypeCode() == null) + throw new IllegalStateException("Amount type not set"); + + if (getAmount() == null) + throw new IllegalStateException("Amount not set"); + + long absAmt= getAmount().movePointRight(getCurrencyMinorUnit()).abs().longValue(); + + return getAccountType() + + getAmountTypeCode() + + getCurrencyCode() + + getCurrencyMinorUnit() + + (getAmount().compareTo(BigDecimal.ZERO) >= 0 ? "C" : "D") + + StringUtils.leftPad(Long.toString(absAmt), 12, '0'); + } + + public static AdditionalAmount parse(String data) { + Objects.requireNonNull(data); + + if (data.length() != SERIALIZED_DATA_LENGTH) + throw new IllegalArgumentException("Invalid data length"); + + String accountType = StringUtils.mid(data, 0, 2); + String amountType = StringUtils.mid(data, 2, 2); + String currencyCode = StringUtils.mid(data, 4, 3); + int minorUnit = Integer.parseInt(StringUtils.mid(data, 7, 1)); + + String amountSign = StringUtils.mid(data, 8, 1); + BigDecimal amount = new BigDecimal(StringUtils.right(data, 12)).movePointLeft(minorUnit); + + if (!"C.D".contains(amountSign)) + throw new IllegalArgumentException("Invalid amount sign"); + + if ("D".equalsIgnoreCase(amountSign)) + amount = amount.negate(); + + return new CMFAdditionalAmount(accountType, amount, currencyCode, amountType, minorUnit); + } + +} diff --git a/modules/cmf/src/main/java/org/jpos/cmf/CMFAmount.java b/modules/cmf/src/main/java/org/jpos/cmf/CMFAmount.java index af6122dc52..5aa852238d 100644 --- a/modules/cmf/src/main/java/org/jpos/cmf/CMFAmount.java +++ b/modules/cmf/src/main/java/org/jpos/cmf/CMFAmount.java @@ -1,6 +1,6 @@ /* * jPOS Project [http://jpos.org] - * Copyright (C) 2000-2021 jPOS Software SRL + * Copyright (C) 2000-2023 jPOS Software SRL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -18,7 +18,6 @@ package org.jpos.cmf; -import org.apache.commons.lang3.BitField; import org.jpos.iso.ISOCurrency; import org.jpos.iso.ISOException; import org.jpos.iso.ISOUtil; @@ -41,7 +40,7 @@ * {@link #serialize(boolean, int)}, that the lengths are in accordance and what you need. *

*

NOTE:This class does not handle additional amounts (DE-054). - * For that, use {@link AdditionalAmount} and {@link AdditionalAmountsWrapper}. + * For that, use {@link org.jpos.iso.AdditionalAmount} and {@link org.jpos.iso.AdditionalAmountsWrapper}. *

* */ diff --git a/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmount.java b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmount.java new file mode 100644 index 0000000000..4de02f77c0 --- /dev/null +++ b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmount.java @@ -0,0 +1,92 @@ +package org.jpos.iso; + +import java.math.BigDecimal; +import java.util.Objects; + +public abstract class AdditionalAmount { + private String accountType; + private String amountType; + private BigDecimal amount; + private String currencyCode; + private int currencyMinorUnit; + + public AdditionalAmount() { + } + + protected AdditionalAmount(String accountType, BigDecimal amount, String currencyCode, + String amountType, int currencyMinorUnit) { + + setAccountType(accountType); + setAmount(amount); + setCurrencyCode(currencyCode); + setAmountTypeCode(amountType); + setCurrencyMinorUnit(currencyMinorUnit); + } + + public abstract String serialize(); + + public String getAccountType() { + return accountType; + } + + public void setAccountType(String accountType) { + Objects.requireNonNull(accountType); + + if (accountType.length() != 2) + throw new IllegalArgumentException("Invalid account type length"); + + this.accountType = accountType; + } + + /** + * @return The internal amount type 2-char string + */ + public String getAmountTypeCode() { + return amountType; + } + + public void setAmountTypeCode(String amountType) { + Objects.requireNonNull(amountType); + this.amountType = amountType; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + Objects.requireNonNull(amount); + this.amount = amount; + } + + public String getCurrencyCode() { + return currencyCode; + } + + public void setCurrencyCode(String currencyCode) { + Objects.requireNonNull(currencyCode); + + if (currencyCode.length() != 3) + throw new IllegalArgumentException("Invalid currency code"); + + this.currencyCode = currencyCode; + } + + public int getCurrencyMinorUnit() { + return currencyMinorUnit; + } + + public void setCurrencyMinorUnit(int currencyMinorUnit) { + if (currencyMinorUnit < 0 || currencyMinorUnit > 9) + throw new IllegalArgumentException("Invalid currency minor unit value"); + + this.currencyMinorUnit = currencyMinorUnit; + } + + @Override + public String toString() { + return getClass().getSimpleName() + + String.format (" {%s,%s,%s,%d,%s}", accountType, amountType, currencyCode, currencyMinorUnit, amount); + } + +} diff --git a/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountType.java b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountType.java new file mode 100644 index 0000000000..5a1b00343f --- /dev/null +++ b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountType.java @@ -0,0 +1,24 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2023 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +public interface AdditionalAmountType { + String code(); + String name(); +} diff --git a/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountTypeConverter.java b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountTypeConverter.java new file mode 100644 index 0000000000..4310820436 --- /dev/null +++ b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountTypeConverter.java @@ -0,0 +1,33 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2023 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +public interface AdditionalAmountTypeConverter { + /** + * Convert from an Additional Amount Type 2-digit code in an 8583 spec into the equivalent jPOS-CMF spec. + * @return may return null if this code is not mapped by CMF. + */ + String toCMF(String code); + + /** + * Convert from an Additional Amount Type 2-digit code in an jPOS-CMF into the equivalent of another 8583 spec. + * @return may return null if the CMF code is not mapped by the external 8583 spec. + */ + String fromCMF(String cmfCode); +} diff --git a/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountsWrapper.java b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountsWrapper.java new file mode 100644 index 0000000000..28b228bfff --- /dev/null +++ b/modules/cmf/src/main/java/org/jpos/iso/AdditionalAmountsWrapper.java @@ -0,0 +1,138 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2023 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Handles additional amounts field content (DE-054 in most 8583 specs) + */ +public class AdditionalAmountsWrapper extends LinkedHashSet { + + private static final long serialVersionUID = 2526355280704001242L; + + /** + * Parses a full string of Additional Amount occurrences (usually DE-054) using a helper + * parser and the given length for each occurrence. + * @param data a String of one or more occurrences of Additional Amount + * @param amtLength the length of each occurrence + * @param parser a function that receives a single occurrence String, and returns an {@link AdditionalAmount} instance + * @return an instance of this class that wraps all the occurrences + */ + public static AdditionalAmountsWrapper parse( + String data, + final int amtLength, + Function parser) + { + if (data.length() % amtLength != 0) + throw new IllegalArgumentException("Invalid length"); + + AdditionalAmountsWrapper amounts = new AdditionalAmountsWrapper(); + + for (int i = 0; i < data.length(); i += amtLength) { + AdditionalAmount amount = parser.apply(StringUtils.mid(data, i, amtLength)); + amounts.add(amount); + } + + return amounts; + } + + public String serialize() { + StringBuilder sb = new StringBuilder(); + forEach(amount -> sb.append(amount.serialize())); + return sb.toString(); + } + + public AdditionalAmount getAny(Predicate p) { + for (AdditionalAmount addAmnt : this) { + if (p.test(addAmnt)) + return addAmnt; + } + return null; + } + + /** + * Returns a list of the additional amounts that match the filter criteria. + * + * If one of the filters is null, it's not considered in the criteria. + * + * @param accountType the account type to filter amounts by. + * @param amountType the amount type to filter amounts by. + * @return a list of the additional amounts that match the filter criteria. + */ + public List listByTypes(String accountType, String amountType) { + ArrayList amounts = new ArrayList<>(); + for (AdditionalAmount amount : this) { + if ((accountType == null || amount.getAccountType().equals(accountType)) && + (amountType == null || amount.getAmountTypeCode().equals(amountType))) { + amounts.add(amount); + } + } + + return amounts; + } + + /** + * Returns true when the wrapper has the given {@code amountType}. + * + * @param amountType the amount type code. + * @return true when the wrapper has the given {@code amountType}. + * @throws NullPointerException if {@code amountType} is {@code null} + */ + public boolean containsAmountType(String amountType) { + Objects.requireNonNull(amountType); + return getAny(a -> amountType.equals(a.getAmountTypeCode())) != null; + } + + /** + * Returns the first occurrence of the additional amount that matches the filter criteria. + * + * @param amountType the amount type to filter amounts by + * @return the first occurrence of the additional amount that matches the filter criteria, or null if none matches + * @throws NullPointerException if {@code amountType} is {@code null} + */ + public AdditionalAmount getFirstByAmountType(String amountType) { + Objects.requireNonNull(amountType); + for (AdditionalAmount addAmnt : this) { + if (amountType.equals(addAmnt.getAmountTypeCode())) + return addAmnt; + } + return null; + } + + /** + * Returns a list of the additional amounts that match the filter criteria. + * + * @param amountType the amount type to filter amounts by. + * @return a list of the additional amounts that match the filter criteria. + * @throws NullPointerException if {@code amountType} is {@code null} + */ + public List listByAmountType(String amountType) { + Objects.requireNonNull(amountType); + return listByTypes(null, amountType); + } + +} diff --git a/modules/cmf/src/test/java/org/jpos/cmf/AdditionalAmountsWrapperTest.java b/modules/cmf/src/test/java/org/jpos/cmf/AdditionalAmountsWrapperTest.java index 57c134dbba..e28865e6db 100644 --- a/modules/cmf/src/test/java/org/jpos/cmf/AdditionalAmountsWrapperTest.java +++ b/modules/cmf/src/test/java/org/jpos/cmf/AdditionalAmountsWrapperTest.java @@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.*; - +@Deprecated public final class AdditionalAmountsWrapperTest { @Test diff --git a/modules/cmf/src/test/java/org/jpos/iso/AdditionalAmountsWrapperTest.java b/modules/cmf/src/test/java/org/jpos/iso/AdditionalAmountsWrapperTest.java new file mode 100644 index 0000000000..ab146e11d0 --- /dev/null +++ b/modules/cmf/src/test/java/org/jpos/iso/AdditionalAmountsWrapperTest.java @@ -0,0 +1,179 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2023 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +import org.junit.jupiter.api.Test; + +import org.jpos.cmf.AmountType; +import org.jpos.cmf.CMFAdditionalAmount; + +import java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public final class AdditionalAmountsWrapperTest { + static AdditionalAmountTypeConverter CONVERTER = AmountType.CONVERTER; + + @Test + public void test_contains_amountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("10.00"), "858", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("12.00"), "858", AmountType.AMOUNT_TAXABLE.code(), 1)); + + assertTrue(wrapper.containsAmountType(AmountType.AMOUNT_TAXABLE.code())); + assertTrue(wrapper.containsAmountType(AmountType.AMOUNT_CASH.code())); + assertFalse(wrapper.containsAmountType(AmountType.AMOUNT_REMAINING_THIS_CYCLE.code())); + } + + @Test + public void test_get_by_amountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("10.00"), "858", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("12.00"), "858", AmountType.AMOUNT_TAXABLE.code(), 1)); + + assertNotNull(wrapper.getFirstByAmountType(AmountType.AMOUNT_TAXABLE.code())); + assertNotNull(wrapper.getFirstByAmountType(AmountType.AMOUNT_CASH.code())); + assertNull(wrapper.getFirstByAmountType(AmountType.AMOUNT_REMAINING_THIS_CYCLE.code())); + } + + + @Test + public void testParseInvalidLengthData() { + String sample = "00028582C00000010000000018582C0000001000"; + assertThrows(IllegalArgumentException.class, + () -> AdditionalAmountsWrapper.parse(sample, CMFAdditionalAmount.SERIALIZED_DATA_LENGTH, CMFAdditionalAmount::parse)); + } + + @Test + public void testSuccessfulParse() { + String sample = "00" + "02" + "858"+"2" + "C"+"000000100000" + + "00" + "01" + "858"+"2" + "C"+"000000100000"; + + AdditionalAmountsWrapper wrapper = AdditionalAmountsWrapper.parse(sample, CMFAdditionalAmount.SERIALIZED_DATA_LENGTH, CMFAdditionalAmount::parse); + assertEquals(2, wrapper.size()); + } + + @Test + public void testParseAndSerialize() { + String sample = "00" + "02" + "840"+"2" + "C"+"000000100000" + + "00" + "01" + "858"+"2" + "C"+"000000100000"; + + AdditionalAmountsWrapper wrapper = AdditionalAmountsWrapper.parse(sample, CMFAdditionalAmount.SERIALIZED_DATA_LENGTH, CMFAdditionalAmount::parse); + assertEquals(sample, wrapper.serialize()); + } + + @Test + public void testParseAndSerializeOneItem() { + String sample = "00" + "01" + "858"+"2" + "C"+"000000100000"; + + AdditionalAmountsWrapper wrapper = AdditionalAmountsWrapper.parse(sample, CMFAdditionalAmount.SERIALIZED_DATA_LENGTH, CMFAdditionalAmount::parse); + + assertEquals(1, wrapper.size()); + assertEquals(sample, wrapper.serialize()); + } + + @Test + public void test_listByAmountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("400.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + + List amounts = wrapper.listByAmountType(AmountType.AMOUNT_SURCHARGE.code()); + + assertNotNull(amounts); + assertEquals(2, amounts.size()); + } + + @Test + public void test_listByTypes_WithAccountAndAmountTypes() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("400.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + + List amounts = wrapper.listByTypes("00", AmountType.AMOUNT_SURCHARGE.code()); + assertNotNull(amounts); + assertEquals(1, amounts.size()); + } + + @Test + public void test_listByTypes_WithNullAccountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("400.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + + List amounts = wrapper.listByTypes(null, AmountType.AMOUNT_SURCHARGE.code()); + assertNotNull(amounts); + assertEquals(2, amounts.size()); + } + + + + @Test + public void test_getFirstByAmountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 1)); + wrapper.add(new CMFAdditionalAmount("30", new BigDecimal("400.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + + AdditionalAmount amount = wrapper.getFirstByAmountType(AmountType.AMOUNT_SURCHARGE.code()); + + assertNotNull(amount); + assertEquals(new BigDecimal("200.00"), amount.getAmount()); + assertEquals("00", amount.getAccountType()); + } + + @Test + public void test_containsAmountType() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 1)); + wrapper.add(new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 1)); + + assertTrue(wrapper.containsAmountType(AmountType.AMOUNT_SURCHARGE.code())); + assertTrue(wrapper.containsAmountType(AmountType.AMOUNT_CASH.code())); + assertFalse(wrapper.containsAmountType(AmountType.AMOUNT_REMAINING_THIS_CYCLE.code())); + } + + @Test + public void toStringTest() { + AdditionalAmountsWrapper wrapper = new AdditionalAmountsWrapper(); + + AdditionalAmount as = new CMFAdditionalAmount("00", new BigDecimal("200.00"), "840", AmountType.AMOUNT_SURCHARGE.code(), 2); + AdditionalAmount ac = new CMFAdditionalAmount("00", new BigDecimal("300.00"), "840", AmountType.AMOUNT_CASH.code(), 2); + wrapper.add(as); + wrapper.add(ac); + + assertEquals("CMFAdditionalAmount {00,42,840,2,200.00}", as.toString()); + assertEquals("CMFAdditionalAmount {00,40,840,2,300.00}", ac.toString()); + assertEquals("[CMFAdditionalAmount {00,42,840,2,200.00}, CMFAdditionalAmount {00,40,840,2,300.00}]", wrapper.toString()); + } +} + + +