From 2fcf145019273ffb980b052f8033353687227de1 Mon Sep 17 00:00:00 2001 From: Markus Jevring Date: Tue, 30 Apr 2024 11:20:29 +0200 Subject: [PATCH] Update children in parents' lists when the children are updated --- .../entities/AbstractEntityManager.java | 7 +- .../stripemock/entities/AccountManager.java | 6 +- .../entities/BalanceTransactionManager.java | 12 ++-- .../entities/BankAccountManager.java | 6 +- .../stripemock/entities/ChargeManager.java | 5 +- .../stripemock/entities/CustomerManager.java | 5 +- .../entities/InvoiceItemManager.java | 6 +- .../stripemock/entities/InvoiceManager.java | 5 +- .../entities/PaymentIntentManager.java | 5 +- .../entities/PaymentMethodManager.java | 5 +- .../stripemock/entities/PayoutManager.java | 23 +++--- .../stripemock/entities/ProductManager.java | 4 +- .../stripemock/entities/RefundManager.java | 6 +- .../entities/SetupIntentManager.java | 4 +- .../stripemock/entities/StripeEntities.java | 70 +++++++++++++++++-- .../entities/SubscriptionManager.java | 6 +- .../stripemock/entities/TransferManager.java | 5 +- .../entities/TransferReversalManager.java | 6 +- 18 files changed, 108 insertions(+), 78 deletions(-) diff --git a/src/main/java/com/sesame/oss/stripemock/entities/AbstractEntityManager.java b/src/main/java/com/sesame/oss/stripemock/entities/AbstractEntityManager.java index e5102c7..1533bb9 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/AbstractEntityManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/AbstractEntityManager.java @@ -21,13 +21,15 @@ abstract class AbstractEntityManager implements E */ protected static final String MAGIC_UPDATE_OPERATION = "__update"; protected final Map entities = new HashMap<>(); + protected final StripeEntities stripeEntities; protected final Clock clock; private final Class entityClass; private final String idPrefix; private final int idLength; - protected AbstractEntityManager(Clock clock, Class entityClass, String idPrefix, int idLength) { + protected AbstractEntityManager(StripeEntities stripeEntities, Clock clock, Class entityClass, String idPrefix, int idLength) { + this.stripeEntities = stripeEntities; this.clock = clock; this.entityClass = entityClass; this.idPrefix = idPrefix; @@ -87,6 +89,8 @@ public final Optional perform(String id, String operation, Map delete(String id) throws ResponseCodeException { @Override public Optional delete(String id, String stripeAccount, String parentEntityType, String parentEntityId) throws ResponseCodeException { - // Most entities do not support related sub-entities, so this is a reasonable default // Most entities do not support related sub-entities, so this is a reasonable default throw new UnsupportedOperationException(String.format("Entity %s does not support sub-entities. Attempted to delete under parent %s/%s", getNormalizedEntityName(), diff --git a/src/main/java/com/sesame/oss/stripemock/entities/AccountManager.java b/src/main/java/com/sesame/oss/stripemock/entities/AccountManager.java index 0a9b7e6..0a7f621 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/AccountManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/AccountManager.java @@ -11,11 +11,8 @@ import java.util.Optional; class AccountManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected AccountManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Account.class, "acct", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Account.class, "acct", 24); } @Override @@ -45,6 +42,7 @@ protected Account initialize(Account account, Map formData, Stri bankAccount.setAccount(account.getId()); externalAccounts.getData() .add(bankAccount); + stripeEntities.bindChildToParentCollection(Account.class, account.getId(), "getExternalAccounts", bankAccount.getId()); } return super.initialize(account, formData, stripeAccount); } diff --git a/src/main/java/com/sesame/oss/stripemock/entities/BalanceTransactionManager.java b/src/main/java/com/sesame/oss/stripemock/entities/BalanceTransactionManager.java index aa8bb49..fca8019 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/BalanceTransactionManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/BalanceTransactionManager.java @@ -9,12 +9,10 @@ import java.util.*; class BalanceTransactionManager extends AbstractEntityManager { - private final Map sourcesByBalanceTransactionId = new HashMap<>(); - private final StripeEntities stripeEntities; + private final Map sourcesByBalanceTransactionId = new HashMap<>(); BalanceTransactionManager(Clock clock, StripeEntities stripeEntities) { - super(clock, BalanceTransaction.class, "txn", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, BalanceTransaction.class, "txn", 24); } @Override @@ -35,14 +33,14 @@ public Optional update(String id, Map formDa @Override public Optional get(String id, String stripeAccount) throws ResponseCodeException { return Optional.ofNullable(sourcesByBalanceTransactionId.get(id)) - .map(source -> BalanceTransactionMapper.toBalanceTransaction(source, stripeAccount)); + .flatMap(stripeEntities::getEntityById) + .map(source -> BalanceTransactionMapper.toBalanceTransaction((BalanceTransactionSource) source, stripeAccount)); } void register(String id, BalanceTransactionSource balanceTransactionSource) { - sourcesByBalanceTransactionId.put(id, balanceTransactionSource); + sourcesByBalanceTransactionId.put(id, balanceTransactionSource.getId()); } - @Override public List list(QueryParameters query, String stripeAccount) throws ResponseCodeException { if (stripeAccount != null) { diff --git a/src/main/java/com/sesame/oss/stripemock/entities/BankAccountManager.java b/src/main/java/com/sesame/oss/stripemock/entities/BankAccountManager.java index f4191e7..0a63d97 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/BankAccountManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/BankAccountManager.java @@ -10,11 +10,9 @@ class BankAccountManager extends AbstractEntityManager { private final Map providedBankAccountNumbers = new HashMap<>(); - private final StripeEntities stripeEntities; BankAccountManager(Clock clock, StripeEntities stripeEntities) { - super(clock, BankAccount.class, "ba", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, BankAccount.class, "ba", 24); } @Override @@ -32,6 +30,7 @@ public BankAccount add(Map formData, String stripeAccount, Strin parentAccount.getExternalAccounts() .getData() .add(bankAccount); + stripeEntities.bindChildToParentCollection(Account.class, parentAccount.getId(), "getExternalAccounts", bankAccount.getId()); return bankAccount; } @@ -79,6 +78,7 @@ public Optional delete(String id, String stripeAccount, String pare parentAccount.getExternalAccounts() .getData() .remove(bankAccount); + stripeEntities.unbindChildFromParentCollection(Account.class, parentAccount.getId(), "getExternalAccounts", bankAccount.getId()); bankAccount.setDeleted(true); return Optional.of(bankAccount); diff --git a/src/main/java/com/sesame/oss/stripemock/entities/ChargeManager.java b/src/main/java/com/sesame/oss/stripemock/entities/ChargeManager.java index 6fa5dfb..f90c79c 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/ChargeManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/ChargeManager.java @@ -11,11 +11,8 @@ import java.util.Map; class ChargeManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - ChargeManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Charge.class, "ch", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Charge.class, "ch", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/CustomerManager.java b/src/main/java/com/sesame/oss/stripemock/entities/CustomerManager.java index 938ffc0..a9b39b6 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/CustomerManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/CustomerManager.java @@ -10,11 +10,8 @@ import java.util.Optional; class CustomerManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected CustomerManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Customer.class, "cus", 14); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Customer.class, "cus", 14); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/InvoiceItemManager.java b/src/main/java/com/sesame/oss/stripemock/entities/InvoiceItemManager.java index 4fdd46c..2b12dac 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/InvoiceItemManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/InvoiceItemManager.java @@ -12,11 +12,8 @@ import java.util.Optional; class InvoiceItemManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected InvoiceItemManager(Clock clock, StripeEntities stripeEntities) { - super(clock, InvoiceItem.class, "ii", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, InvoiceItem.class, "ii", 24); } @Override @@ -29,6 +26,7 @@ protected InvoiceItem initialize(InvoiceItem invoiceItem, Map fo invoice.getLines() .getData() .add(convertToLineItem(invoiceItem)); + stripeEntities.bindChildToParentCollection(Invoice.class, invoiceId, "getLines", invoiceItem.getId()); } return super.initialize(invoiceItem, formData, stripeAccount); } diff --git a/src/main/java/com/sesame/oss/stripemock/entities/InvoiceManager.java b/src/main/java/com/sesame/oss/stripemock/entities/InvoiceManager.java index 077772a..b889518 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/InvoiceManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/InvoiceManager.java @@ -14,11 +14,8 @@ import java.util.Optional; class InvoiceManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected InvoiceManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Invoice.class, "in", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Invoice.class, "in", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/PaymentIntentManager.java b/src/main/java/com/sesame/oss/stripemock/entities/PaymentIntentManager.java index 425db3b..20d776e 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/PaymentIntentManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/PaymentIntentManager.java @@ -10,11 +10,8 @@ import java.util.Objects; class PaymentIntentManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - PaymentIntentManager(Clock clock, StripeEntities stripeEntities) { - super(clock, PaymentIntent.class, "pi", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, PaymentIntent.class, "pi", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/PaymentMethodManager.java b/src/main/java/com/sesame/oss/stripemock/entities/PaymentMethodManager.java index 58b2d7a..c12a1a1 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/PaymentMethodManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/PaymentMethodManager.java @@ -15,11 +15,8 @@ class PaymentMethodManager extends AbstractEntityManager { // todo: test methods for things like charge_declined etc - private final StripeEntities stripeEntities; - PaymentMethodManager(Clock clock, StripeEntities stripeEntities) { - super(clock, PaymentMethod.class, "pm", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, PaymentMethod.class, "pm", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/PayoutManager.java b/src/main/java/com/sesame/oss/stripemock/entities/PayoutManager.java index 887d5a6..1283061 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/PayoutManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/PayoutManager.java @@ -7,25 +7,20 @@ import com.stripe.model.*; import java.time.Clock; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; class PayoutManager extends AbstractEntityManager { - private final Map> byStripeAccount = new HashMap<>(); - private final StripeEntities stripeEntities; + private final Map> stripeAccountToPayoutId = new HashMap<>(); PayoutManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Payout.class, "po", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Payout.class, "po", 24); } @Override public Payout add(Map formData, String stripeAccount) throws ResponseCodeException { Payout payout = super.add(formData, stripeAccount); - byStripeAccount.computeIfAbsent(stripeAccount, ignored -> new ArrayList<>()) - .add(payout); + stripeAccountToPayoutId.computeIfAbsent(stripeAccount, ignored -> new HashSet<>()) + .add(payout.getId()); return payout; } @@ -130,13 +125,17 @@ public List list(QueryParameters query, String stripeAccount) { .stream() .toList(); } else { - return byStripeAccount.getOrDefault(stripeAccount, new ArrayList<>()); + Set payoutIdsForStripeAccount = stripeAccountToPayoutId.getOrDefault(stripeAccount, new HashSet<>()); + return entities.values() + .stream() + .filter(payout -> payoutIdsForStripeAccount.contains(payout.getId())) + .toList(); } } @Override public void clear() { super.clear(); - byStripeAccount.clear(); + stripeAccountToPayoutId.clear(); } } diff --git a/src/main/java/com/sesame/oss/stripemock/entities/ProductManager.java b/src/main/java/com/sesame/oss/stripemock/entities/ProductManager.java index 7e18dec..4bae996 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/ProductManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/ProductManager.java @@ -7,8 +7,8 @@ import java.util.Optional; class ProductManager extends AbstractEntityManager { - protected ProductManager(Clock clock) { - super(clock, Product.class, "prod", 24); + protected ProductManager(Clock clock, StripeEntities stripeEntities) { + super(stripeEntities, clock, Product.class, "prod", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/RefundManager.java b/src/main/java/com/sesame/oss/stripemock/entities/RefundManager.java index 37b6131..57f30c5 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/RefundManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/RefundManager.java @@ -13,11 +13,8 @@ import java.util.Objects; class RefundManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected RefundManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Refund.class, "re", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Refund.class, "re", 24); } @Override @@ -41,6 +38,7 @@ protected Refund initialize(Refund refund, Map formData, String charge.getRefunds() .getData() .add(refund); + stripeEntities.bindChildToParentCollection(Charge.class, charge.getId(), "getRefunds", refund.getId()); if (refund.getAmount() == null) { refund.setAmount(charge.getAmount()); charge.setAmountRefunded(charge.getAmount()); diff --git a/src/main/java/com/sesame/oss/stripemock/entities/SetupIntentManager.java b/src/main/java/com/sesame/oss/stripemock/entities/SetupIntentManager.java index f70c61a..99b066f 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/SetupIntentManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/SetupIntentManager.java @@ -8,8 +8,8 @@ import java.util.Map; class SetupIntentManager extends AbstractEntityManager { - protected SetupIntentManager(Clock clock) { - super(clock, SetupIntent.class, "seti", 24); + protected SetupIntentManager(Clock clock, StripeEntities stripeEntities) { + super(stripeEntities, clock, SetupIntent.class, "seti", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/StripeEntities.java b/src/main/java/com/sesame/oss/stripemock/entities/StripeEntities.java index 9b7ef9a..2c3c009 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/StripeEntities.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/StripeEntities.java @@ -2,17 +2,18 @@ import com.sesame.oss.stripemock.http.ResponseCodeException; import com.stripe.model.HasId; +import com.stripe.model.StripeCollection; import com.stripe.net.ApiResource; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.time.Clock; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; +import java.util.*; public class StripeEntities { private final Map, EntityManager> entityManagers = new HashMap<>(); private final Map> entityManagersByNormalizedEntityName = new HashMap<>(); + private final Map> childToParentCollectionMappings = new HashMap<>(); public StripeEntities(Clock clock) { // As these entity managers will need to have access to each other, often in a circular dependency fashion, @@ -24,7 +25,7 @@ public StripeEntities(Clock clock) { add(new PaymentIntentManager(clock, this)); add(new SubscriptionManager(clock, this)); add(new RefundManager(clock, this)); - add(new SetupIntentManager(clock)); + add(new SetupIntentManager(clock, this)); add(new TransferManager(clock, this)); add(new CustomerManager(clock, this)); add(new InvoiceManager(clock, this)); @@ -33,7 +34,7 @@ public StripeEntities(Clock clock) { add(new PayoutManager(clock, this)); add(new BalanceTransactionManager(clock, this)); add(new BankAccountManager(clock, this)); - add(new ProductManager(clock)); + add(new ProductManager(clock, this)); add(new AccountManager(clock, this)); } @@ -64,6 +65,7 @@ public void clear() { entityManager.clear(); entityManager.bootstrap(); } + childToParentCollectionMappings.clear(); } public Optional getEntityById(String id) { @@ -76,6 +78,58 @@ public Optional getEntityById(String id) { .findAny(); } + void bindChildToParentCollection(Class parentEntityType, String parentEntityId, String collectionGetterName, String childEntityId) { + childToParentCollectionMappings.computeIfAbsent(childEntityId, k -> new ArrayList<>()) + .add(new ParentCollection(parentEntityType, parentEntityId, collectionGetterName)); + } + + void unbindChildFromParentCollection(Class parentEntityType, String parentEntityId, String collectionGetterName, String childEntityId) { + childToParentCollectionMappings.computeIfAbsent(childEntityId, k -> new ArrayList<>()) + .remove(new ParentCollection(parentEntityType, parentEntityId, collectionGetterName)); + } + + /** + * This somewhat ugly hack solves the issue that entity references are not permanent. Any update to an entity recreates that entity from scratch. + * This means that any collections etc that held that entity will now hold an old object. We need to make sure that children in collections held + * by parents are updated. + * + * @param childEntity the child that was just updated + * @param

the type of the parent + * @param the type of the child + */ +

void updateLists(C childEntity) { + List parentCollectionsThatReferenceTheChild = childToParentCollectionMappings.get(childEntity.getId()); + if (parentCollectionsThatReferenceTheChild == null) { + return; + } + try { + for (ParentCollection parentCollectionThatReferencesTheChild : parentCollectionsThatReferenceTheChild) { + Class

parentType = (Class

) parentCollectionThatReferencesTheChild.parentEntityType(); + EntityManager

parentEntityManager = getEntityManager(parentType); + P parent = parentEntityManager.get(parentCollectionThatReferencesTheChild.parentEntityId(), null) + .orElseThrow(); + // It would have been nice to use something like Charge::getRefunds here, but it was too hard to get it to work + // with the types, since it would be in a collection anyway. Maybe there's a way to make it work, but for now, + // strings are going to have to do. + // Here I am, painting myself into a smaller and smaller corner with my design decisions =) + Method method = parentCollectionThatReferencesTheChild.parentEntityType() + .getMethod(parentCollectionThatReferencesTheChild.collectionGetterName()); + StripeCollection collection = (StripeCollection) method.invoke(parent); + List list = collection.getData(); + for (int i = 0; i < list.size(); i++) { + if (list.get(i) + .getId() + .equals(childEntity.getId())) { + list.set(i, childEntity); + } + } + } + } catch (ResponseCodeException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + // This shouldn't happen + throw new AssertionError(e); + } + } + private static Object safeGet(String id, EntityManager entityManager) { try { return entityManager.get(id, null) @@ -86,4 +140,8 @@ private static Object safeGet(String id, EntityManager entityManager) { return null; } } + + private record ParentCollection(Class parentEntityType, + String parentEntityId, + String collectionGetterName) {} } diff --git a/src/main/java/com/sesame/oss/stripemock/entities/SubscriptionManager.java b/src/main/java/com/sesame/oss/stripemock/entities/SubscriptionManager.java index 0cd5451..dc6924c 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/SubscriptionManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/SubscriptionManager.java @@ -12,11 +12,8 @@ import java.util.Optional; class SubscriptionManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - SubscriptionManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Subscription.class, "sub", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Subscription.class, "sub", 24); } @Override @@ -41,6 +38,7 @@ protected Subscription initialize(Subscription subscription, Map firstInvoice.getLines() .getData() .add(toInvoiceLineItem(subscriptionItem)); + stripeEntities.bindChildToParentCollection(Invoice.class, firstInvoice.getId(), "getLines", subscriptionItem.getId()); } // invoices that are part of a subscription are automatically finalized, meaning that they can't change. // This moves them from 'draft' to 'open' diff --git a/src/main/java/com/sesame/oss/stripemock/entities/TransferManager.java b/src/main/java/com/sesame/oss/stripemock/entities/TransferManager.java index 4bfde40..62d270c 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/TransferManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/TransferManager.java @@ -13,11 +13,8 @@ import java.util.Map; class TransferManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected TransferManager(Clock clock, StripeEntities stripeEntities) { - super(clock, Transfer.class, "tr", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, Transfer.class, "tr", 24); } @Override diff --git a/src/main/java/com/sesame/oss/stripemock/entities/TransferReversalManager.java b/src/main/java/com/sesame/oss/stripemock/entities/TransferReversalManager.java index b06af15..142569d 100644 --- a/src/main/java/com/sesame/oss/stripemock/entities/TransferReversalManager.java +++ b/src/main/java/com/sesame/oss/stripemock/entities/TransferReversalManager.java @@ -14,11 +14,8 @@ import java.util.Optional; class TransferReversalManager extends AbstractEntityManager { - private final StripeEntities stripeEntities; - protected TransferReversalManager(Clock clock, StripeEntities stripeEntities) { - super(clock, TransferReversal.class, "trr", 24); - this.stripeEntities = stripeEntities; + super(stripeEntities, clock, TransferReversal.class, "trr", 24); } @Override @@ -41,6 +38,7 @@ public TransferReversal add(Map formData, String stripeAccount, parentTransfer.getReversals() .getData() .add(transferReversal); + stripeEntities.bindChildToParentCollection(Transfer.class, parentTransfer.getId(), "getReversals", transferReversal.getId()); long totalAmountReversed = parentTransfer.getReversals() .getData() .stream()